Come codificare porte e serrature

Nei giochi fatti di stanze collegate come The Legend of Zelda, il più recente The Binding of Isaac, o qualsiasi tipo di Roguelike o anche di tipo Metroidvania, le porte giocano un ruolo essenziale nella navigazione e nel progresso del giocatore.

Le porte consentono al giocatore di spostarsi da una stanza o un livello a un altro, e così hanno un posto importante nella navigazione e nella connessione delle diverse stanze l'una all'altra, e nella definizione della mappa come un mondo aperto o un piano sotterraneo. Possono anche fungere da posti di blocco temporanei che il giocatore dovrà sbloccare tramite una specifica meccanica (come ottenere una chiave o attivare un interruttore).

In questo tutorial mostrerò diverse meccaniche di blocco e proporrò modi per implementarle nei tuoi giochi. Queste non sono in alcun modo destinate a essere le uniche o migliori implementazioni; sono esempi pratici.

Le demo interattive in questo tutorial sono state realizzate con lo strumento di creazione di giochi HTML5 Construct 2 e dovrebbero essere compatibili con la sua versione gratuita. (I file CAPX sono disponibili nel download sorgente.) Tuttavia, questo tutorial dovrebbe aiutarti a imparare come implementare le porte e la logica dei blocchi in qualsiasi motore tu voglia. Una volta ottenuta l'idea alla base della logica, tutto dipende dalla tua conoscenza del tuo strumento / linguaggio di codifica e dal modo in cui desideri adattarlo al gioco che stai facendo attualmente.

Tuffiamoci dentro!

Il meccanico di base

Una porta è fondamentalmente un blocco di scenario che non può essere superato, impedendo al personaggio del giocatore di passare attraverso finché non viene sbloccato. La porta può avere diversi stati: bloccati o sbloccati, chiusi o aperti.

Ci deve essere una rappresentazione ovvia di quest'ultimo; il giocatore deve essere in grado di dire che la porta è in realtà una porta e se è nel suo stato bloccato o sbloccato.

Nelle seguenti demo, le porte sono presentate attraverso due grafici:


Questa è una porta chiusa.

Questa è una porta aperta.

Ho anche usato colori diversi per rappresentare i diversi materiali di cui potrebbero essere fatte le porte, ma, ad essere onesti, l'aspetto grafico dipende da te, dal tuo gioco e dal suo universo. La parte più importante è che la porta dovrebbe essere chiaramente identificabile come una porta, e dovrebbe essere ovvio se bloccherà la progressione o l'apertura del giocatore e porterà al resto del livello o del mondo.

Quando è chiuso o bloccato, la porta deve essere un blocco di stato solido. Quando è aperto, lo stato solido deve essere disabilitato, consentendo ai personaggi di attraversarlo. Assicurati che qualunque sia il tuo motore di collisione, ti permette di modificare questo stato al volo abbastanza facilmente.

Da un punto di vista della programmazione, l'oggetto porta dovrebbe contenere o essere collegato a un è bloccato Variabile booleana. A seconda del valore di questa variabile, è possibile determinare quale sprite visualizzare e se il blocco deve essere solido o meno.

Per sbloccare la porta, il personaggio deve contenere a has_key Variabile booleana stessa quando il giocatore ha raccolto una chiave: vero se ce l'hanno, falso se non lo fanno.

In questa meccanica di base, la chiave agisce come parte dell'inventario del personaggio e si apre una chiave tutti porte. Usare la chiave su una porta non la consuma; la chiave rimane nell'inventario del personaggio.

Per visualizzarlo, possiamo semplicemente visualizzare un'immagine della chiave nell'HUD per far sapere al giocatore che "possiede" una chiave che potrebbe aprire le porte una volta che il personaggio l'ha afferrata (spostando il personaggio sopra la chiave nella stanza ).

Considera il seguente esempio di base:

Fai clic sulla demo per focalizzarla, quindi controlla il personaggio usando i tasti freccia della tua tastiera ed esegui azioni con la barra spaziatrice. (In questo esempio, l'azione è "apri una porta".)

I muri sono blocchi solidi che non permettono al personaggio di attraversare quando si scontrano con loro. Anche le porte chiuse sono solide.

Per aprire una porta, il personaggio deve trovarsi entro 64 pixel dalla porta e possedere una chiave (cioè, la has_key La variabile booleana che determina se il personaggio ha la chiave nel proprio inventario deve essere vero). 

Con queste condizioni, quando il giocatore preme la barra spaziatrice, lo stato della porta appropriata viene modificato. La sua variabile booleana bloccato è impostato per falso, e il suo stato "solido" è disabilitato.

In pseudocodice, questo dovrebbe assomigliare a qualcosa:

Door.Locked = True Door.AnimationFrame = 0 // La cornice dell'animazione che visualizza la porta come bloccata. Door.Solid = Enabled // Lo stato solido della porta è abilitato Door.Locked = False Door.AnimationFrame = 1 // L'animazione frame che visualizza la porta come aperta Door.Solid = Disabled // Lo stato solido della porta è disabilitato Keyboard Key "Space" viene premuto e Distance (Character, Door) <= 64px and Door.Locked = True and Character.Has_Key = True //The player has a key Door.Locked = False Keyboard Key "Space" is pressed and Distance(Character,Door) <= 64px and Door.Locked = True and Character.Has_Key = False //The player does not have a key Text.text = "You don't have a key for that door" 

Promemoria: questo codice non rappresenta una lingua specifica; dovresti essere in grado di implementarlo in qualsiasi lingua tu voglia.

Puoi anche notare che eseguiamo un controllo per quando il giocatore lo fa non avere la chiave prevista e visualizzare un messaggio di feedback che spiega perché la porta non è stata sbloccata. Puoi gestire assegni come quelli che meglio si addicono al tuo gioco, ma tieni presente che è sempre bello dare feedback al tuo giocatore che la sua azione è stata registrata e spiegare il motivo per cui non è stata completata.

Questa è una logica di porta e blocco molto semplice e come implementarla. Nel resto del tutorial, vedremo altri sistemi di blocco che sono varianti di questo sistema di base.

Diversi sistemi di chiusura

Abbiamo visto il sistema di base in cui la chiave è un'intera parte dell'inventario del personaggio e una chiave apre tutte le porte e può essere riutilizzata per aprire diverse porte. Costruiamoci su questo.

Esempio di KeyStack

Nel prossimo esempio, il personaggio avrà a pila di chiavi nel suo inventario. Anche se ci sono diversi colori della porta, la differenza è strettamente grafica qui: l'oggetto della porta è logicamente lo stesso dell'esempio di base e un tipo di chiave può aprirne uno qualsiasi. Tuttavia, questa volta, ogni volta che si utilizza una chiave per aprire una porta, tale chiave viene rimossa dalla pila.


Per quanto riguarda la codifica, questa modifica è principalmente a livello del personaggio. Invece di avere un has_key Variabile booleana, ti consigliamo di avere un numerico variabile che terrà il numero di chiavi che il personaggio ha "in stock".

Ogni volta che il personaggio prende una chiave, aggiungi 1 a questa variabile per rappresentare lo stack in aumento. Ogni volta che il personaggio apre una porta, sottrai 1 da questa variabile per rappresentare l'uso di una chiave. (Nel terreno dei videogiochi, le chiavi vengono distrutte non appena vengono utilizzate una volta.)

Un'altra modifica è quando il giocatore preme la barra spaziatrice: invece di controllare che a has_key La variabile booleana è vero, in realtà vogliamo verificare che il valore di KeyStack è più di zero, in modo che possiamo consumare una chiave dopo aver aperto la porta.

In pseudocodice, questo assomiglia a qualcosa:

Meccanica delle porte = uguale all'esempio di base sopra. Tasto Tastiera "Spazio" viene premuto e Carattere. KEYStack> 0 e Distanza (Carattere, Porta) <= 64 and Door.Locked = True Character.KeyStack = Character.KeyStack - 1 Door.Locked = False 

Quale esempio

In questo nuovo esempio, prenderemo in considerazione uno scenario in cui diversi tipi di porte richiedono diversi tipi di chiavi da sbloccare.

Qui, come nel primo esempio di base, le chiavi faranno parte dell'inventario del personaggio. Torneremo a utilizzare le variabili booleane per determinare se il personaggio ha raccolto le chiavi richieste. E poiché avremo chiavi diverse, avremo anche diversi tipi di porte (porta nera, porta rossa, porta d'oro) che richiederà anche una chiave appropriata per consentirne l'apertura (chiave nera, chiave rossa, chiave d'oro, rispettivamente ).

Gli oggetti della porta useranno sprite diversi per mostrare il loro materiale e conterranno una variabile numerica chiamata WhichKey questo indicherà il tipo di chiave che ci si aspetta e il tipo di grafico che dovrebbe essere visualizzato. I diversi valori chiave sono contenuti come variabili costanti, per una migliore leggibilità.


In pseudocodice:

COSTANTE BLACK_KEY = 0 COSTANTE RED_KEY = 1 COSTANTE GOLD_KEY = 2 Le meccaniche di porta sono le stesse dell'esempio di base. Tasto tastiera "Spazio" viene premuto // La porta richiede una chiave nera ma il personaggio non ne ha uno Se Door.Locked = True e Door.WhichKey = BLACK_KEY e Character.Has_Black_Key = False e Distance (Door, Character) <= 64 Text.text="You need a black key for this door" //The door requires a red key but the character doesn't have one Else If Door.Locked = True and Door.WhichKey = RED_KEY and Character.Has_Red_Key = False and Distance(Door,Character) <= 64 Text.text="You need a red key for this door" //The door requires a gold key but the character doesn't have one Else If Door.Locked = True and Door.WhichKey = GOLD_KEY and Character.Has_Gold_Key = False and Distance(Door,Character) <= 64 Text.text="You need a red key for this door" //The door requires a black key and the character has one Else If Door.Locked = True and Door.WhichKey = BLACK_KEY and Character.Has_Black_Key = True and Distance(Door,Character) <= 64 Door.Locked = False //The door requires a red key and the character has one Else If Door.Locked = True and Door.WhichKey = RED_KEY and Character.Has_Red_Key = True and Distance(Door,Character) <= 64 Door.Locked = False //The door requires a gold key and the character has one Else If Door.Locked = True and Door.WhichKey = GOLD_KEY and Character.Has_Gold_Key = True and Distance(Door,Character) <= 64 Door.Locked = False

Questa è una variante dell'esempio di base che consente diversi tipi di chiavi e porte e che non consuma chiavi per aprire le porte. Una volta che hai la chiave, fa parte del tuo inventario, parte delle "statistiche" del personaggio.

Esempio di interruttore

Questa volta, invece di agire direttamente sulle porte, il giocatore deve attivare un interruttore specifico per aprire o chiudere una porta specifica.

Le porte qui sono essenzialmente lo stesso oggetto dell'esempio di base. Potrebbero visualizzare grafici diversi, ma la logica dell'oggetto è sempre la stessa. C'è un'aggiunta però: aggiungiamo due variabili numeriche DoorID e SwitchID, che usiamo per sapere quale interruttore è legato a quale porta.

Interruttori sono un nuovo tipo di oggetti che ho scelto di rendere solido (ma non devi). Contengono una variabile booleana, attivato, e variabili numeriche DoorID e SwitchID che, come puoi intuire, usiamo per determinare quale interruttore è legato a quale porta.

Quindi, quando un interruttore ha Attivato: vero, la porta "collegata" è impostata per avere Bloccato: Falso. La nostra azione con la barra spaziatrice avverrà quando si troverà accanto a a interruttore, piuttosto che vicino a una porta. Notare l'assenza di una chiave in questo esempio, poiché gli interruttori agiscono come chiavi:

Potremmo semplicemente usare un semplice codice che controlla i collegamenti della porta-interruttore nella stessa stanza (poiché questo esempio mostra tre porte e interruttori nella stessa stanza), ma in seguito vedremo che potremmo avere interruttori che agiscono sulle porte che si trovano in un altro stanza, e quindi la loro azione non si verificherà nel momento esatto in cui il giocatore attiva l'interruttore; si verificherà più tardi, quando viene caricata la nuova stanza.

Per questo motivo, abbiamo bisogno persistenza. Un'opzione per questo è usare gli array per tenere traccia dei dati come lo stato degli switch (cioè, se ciascun interruttore è attivato o meno).

In pseudocodice:

COSTANTE SWITCH_DOORID = 0 COSTANTE SWITCH_ACTIVATION = 1 // Quelle costanti ci permetteranno di mantenere un promemoria leggibile delle coordinate dell'array 
// Definisce un array // La coordinata X dell'array corrisponderà al valore SwitchID // La coordinata Y-0 sarà il DoorID // La coordinata Y-1 sarà lo stato di attivazione aSwitch (numero di interruttori, 2) // 2 è il numero dell'altezza (Y), spesso basato su 0.
Esegui un'associazione degli SwitchID con DoorIDs Il meccanismo di porta è ancora lo stesso dell'esempio di base. // Visualizzazione del grafico dell'interruttore corretto in base al relativo stato di attivazione Switch.Activated = True Visualizza il frame di animazione Switch_ON Switch.Activated = False Visualizza il frame di animazione Switch_OFF Keyboard Key "Space" viene premuto e Distance (Character, Switch) <= 64 Switch.Toggle(Activated) //A function that will set the value to either True or False) aSwitch(Switch.SwitchID,SWITCH_ACTIVATION) = Switch.Activated //It can depend on your coding language, but the idea is to set the value in the array where X is the SwitchID and where Y is the state of activation of the switch. The value itself is supposed to be the equivalent of the Switch.Activated boolean value. Door.DoorID = aSwitch(Switch.SwitchID,SWITCH.DOORID) //Allows us to make sure we're applying/selecting the correct door instance //Now according to the activation value, we lock or unlock the door aSwitch(Switch.SwitchID,SWITCH.ACTIVATION) = True Door.Locked = False aSwitch(Switch.SwitchID,SWITCH.ACTIVATION) = False Door.Locked = True

Per questo esempio specifico, in cui gli interruttori si trovano nella stessa stanza delle porte a cui sono collegati, l'uso della tecnica dell'array è eccessivo. Se il tuo gioco è impostato in modo tale che ogni interruttore che agisce su una porta verrà posizionato nella stessa stanza, quindi scegli il metodo più semplice, sbarazzati dell'array e controlla gli oggetti che si trovano su solo schermo.

Esempio di interruttore a piastra

Gli interruttori a piastre sono simili agli interruttori, nel senso che sono attivati ​​o meno, e che possiamo collegarli alle porte per bloccarli o sbloccarli. La differenza sta nel modo in cui viene attivato un interruttore a piastra, che è attraverso la pressione.

In questo esempio di vista dall'alto, il selettore della piastra verrà attivato ogni volta che il carattere si sovrappone a esso. Puoi premere la barra spaziatrice per far cadere una pietra sull'interruttore a piastra, lasciandola attiva anche quando il personaggio non è su di essa.

L'implementazione di questo è simile all'esempio precedente, con due piccole modifiche:

  • Devi attivare il commutatore a piastra quando un personaggio o una roccia è sopra di esso.
  • Devi fare in modo che la barra spaziatrice lasci cadere una roccia (dall'inventario) sull'interruttore della piastra.
// La maggior parte dell'implementazione è la stessa dell'esempio precedente sostituire l'oggetto Switch con oggetto PlateSwitch // Plate-Switch Mechanic Character OPPURE Rock NON si sovrappone PlateSwitch PlateSwitch.Activated = False aSwitch (PlateSwitch.SwitchID, SWITCH_ACTIVATION) = PlateSwitch.Activated Door.DoorID = aSwitch (PlateSwitch.SwitchID, SWITCH.DOORID) // Permette di verificare che stiamo applicando / selezionando l'istanza della porta corretta. Door.Locked = True Character OPPURE Rock si sovrappone PlateSwitch PlateSwitch.Activated = True aSwitch (PlateSwitch .SwitchID, SWITCH_ACTIVATION) = PlateSwitch.Activated Door.DoorID = aSwitch (PlateSwitch.SwitchID, SWITCH.DOORID) // Permette di verificare che stiamo applicando / selezionando l'istanza della porta corretta. Door.Locked = False Keyboard Key "Space" viene premuto E Il personaggio si sovrappone a PlateSwitch Spawn Rock at PlateSwitch.position 

Esempio di mob

Un'altra possibile meccanica di blocco è quella di richiedere al giocatore di sbarazzarsi di tutti i nemici (noti anche come mob) in una stanza o area per attivare lo sblocco delle porte.

Per questo esempio, ho creato alcune aree in una singola stanza; ogni area ha una porta e molti mob (anche se quei nemici non si muovono e non infliggono danno).
Ogni area ha il suo colore.

La barra spaziatrice farà sparare al personaggio alcuni proiettili; tre proiettili uccideranno un mob.

Questo tipo di meccanica è usato in The Legend of Zelda e The Binding of Isaac, e ruota intorno a una funzione che controlla il numero di nemici viventi nella stanza o nell'area. In questo esempio, ciascuna area colorata contiene un conteggio dei mob viventi, iniziata quando la stanza si carica ed è legata alla porta. La morte di ciascun mob sottrae 1 da questo contatore; una volta che scende a 0, le porte Bloccato stato è cambiato in falso.

// All'inizio del gioco Per ogni area Per ogni Mob Area Area sovrapposta. AliveMobs = Area.AliveMobs + 1 
Il meccanico della porta è lo stesso dell'esempio di base
Tasto Tastiera "Spazio" viene premuto Crea un proiettile dalla posizione del personaggio. Il proiettile si scontra con Mob Mob.HP = Mob.HP - 1 Distruggi il proiettile Mob.HP <=0 //Mob is dead and Mob is overlapping Area Destroy Mob Area.AliveMobs = Area.AliveMobs - 1 Area.AliveMobs <= 0 and Door is linked to Area //By means of an ID, a pointer or whatever Door.Locked = False

In questo esempio, a La zona è uno sprite colorato con una variabile numerica associata, AliveMobs, che conta il numero di mob sovrapposti all'area. Una volta che tutti i mob in un'area sono stati sconfitti, la porta corrispondente è sbloccata (usando la stessa meccanica che abbiamo visto dall'esempio di base).

Esempio di navigazione

Come accennato nell'introduzione, le porte possono fungere da ostacoli di blocco, ma possono anche essere utilizzate per consentire al personaggio del giocatore di navigare da una stanza all'altra.

In questo esempio, le porte verranno sbloccate di default poiché siamo più interessati all'aspetto di navigazione.

La meccanica dipende molto dal gioco che stai facendo, così come dal modo in cui gestisci la struttura dei dati per i tuoi piani. Non entrerò nei dettagli di come funziona la mia implementazione qui, in quanto è molto specifica per Construct 2, ma puoi trovarla nei file di origine se desideri.

Conclusione

In questo articolo, abbiamo visto come le porte sono ostacoli temporanei che richiedono chiavi o meccanismi di sblocco come interruttori, interruttori a piastre o anche la morte dei mob. Abbiamo anche visto come possono agire come "ponti", consentendo la navigazione attraverso diverse aree del mondo del gioco.

Come promemoria rapido, ecco alcune possibili meccaniche di blocco:

  • Una chiave per tutte le porte, come parte dell'inventario.
  • Chiavi di consumo: ogni volta che apri una porta, una chiave viene rimossa dalla tua pila di chiavi.
  • Porte diverse richiedono chiavi diverse.
  • Interruttori o interruttori a piastra, in cui non si agisce direttamente sulla porta per sbloccarla ma tramite un dispositivo separato collegato.
  • Uccidere tutti i mob di un'area sblocca automaticamente una porta.

Se hai mescolato tutte quelle meccaniche in un gioco, potresti finire con qualcosa del genere:

Qui abbiamo una buona selezione di diverse meccaniche di porte e chiusure, che richiedono al giocatore di attraversare diverse stanze per sbloccare le varie porte. Ai fini dell'apprendimento, è possibile riprodurlo nel proprio ambiente di programmazione, utilizzando tutte le precedenti implementazioni che abbiamo eseguito.

Spero che questo articolo ti sia piaciuto e che sia stato utile per te, e vorrei ricordare che puoi trovare la fonte di tutte le demo su Github. Puoi aprirli e modificarli nella versione gratuita di Construct 2 (versione r164.2 o successiva).

Riferimenti

  • Immagine di anteprima: Lock progettata da João Miranda del progetto Noun