Hoe deuren en vergrendelingen te coderen

In games gemaakt van verbonden kamers zoals The Legend of Zelda, de meer recente The Binding of Isaac, of elk type Roguelike of zelfs Metroidvania-achtige, deuren spelen een essentiële rol in de navigatie en voortgang van de speler.

Deuren laten de speler van de ene kamer of het ene niveau naar het andere reizen, en hebben dus een belangrijke plaats in de navigatie en de verbinding van de verschillende kamers met elkaar, en in het definiëren van de kaart als een open wereld of een kerkerbodem. Ze kunnen ook fungeren als tijdelijke wegversperringen die de speler moet ontgrendelen via een specifieke monteur (zoals het verkrijgen van een sleutel of het activeren van een schakelaar).

In deze tutorial zal ik verschillende lock-mechanismen demonstreren en manieren voorstellen om ze in je games te implementeren. Deze zijn op geen enkele manier bedoeld als de enige of beste implementaties; het zijn praktische voorbeelden.

De interactieve demo's in deze tutorial zijn gemaakt met de HTML5 game maker tool Construct 2 en zouden compatibel moeten zijn met de gratis versie. (De CAPX-bestanden zijn beschikbaar in de brondownload.) Deze tutorial zou u echter moeten helpen de logica van deuren en sloten te leren kennen in welke engine u maar wilt. Zodra je het idee achter de logica krijgt, is het allemaal afhankelijk van je eigen kennis van je coderingstool / taal en de manier waarop je het wilt aanpassen aan het spel dat je momenteel aan het maken bent..

Laten we erin duiken!

De basismonteur

Een deur is in feite een blok van landschappen dat niet kan worden gepasseerd, waardoor wordt voorkomen dat het personage van de speler doorloopt tot het wordt ontgrendeld. De deur kan verschillende toestanden hebben: vergrendeld of ontgrendeld, gesloten of open.

Er moet een duidelijke weergave zijn van de laatste; de speler moet kunnen zien dat de deur eigenlijk een deur is en of deze in de vergrendelde of ontgrendelde staat is.

In de volgende demo's worden de deuren gepresenteerd via twee afbeeldingen:


Dit is een gesloten deur.

Dit is een open deur.

Ik heb ook verschillende kleuren gebruikt om de verschillende materialen weer te geven waaruit de deuren kunnen bestaan, maar om eerlijk te zijn, het grafische aspect is aan jou, je spel en het universum. Het belangrijkste is dat de deur duidelijk herkenbaar moet zijn als een deur, en het moet duidelijk zijn of het de voortgang van de speler zal blokkeren of zal openen en zal leiden naar de rest van het level of de wereld..

In gesloten of gesloten toestand moet de deur een blok van vaste toestand zijn. Wanneer deze is geopend, moet de solid-state worden uitgeschakeld, zodat tekens er doorheen kunnen gaan. Zorg ervoor dat alles wat uw botsingmotor is, u toestaat om deze toestand vrij snel aan te passen.

Vanuit een programmeringsperspectief moet het deurobject een of een link bevatten naar of bevatten is gesloten Booleaanse variabele. Afhankelijk van de waarde van deze variabele, kunt u bepalen welke sprite moet worden weergegeven en of het blok al dan niet degelijk moet zijn.

Om de deur te ontgrendelen, moet het personage een bevatten has_key Booleaanse variabele zelf wanneer de speler een sleutel heeft opgehaald: waar als ze het hebben, vals als ze dat niet doen.

In deze basismonteur fungeert de sleutel als een deel van de inventaris van het personage en wordt één sleutel geopend allemaal deuren. Het gebruik van de sleutel op een deur verbruikt het niet; de sleutel blijft in de inventaris van het personage.

Om het te visualiseren, kunnen we eenvoudig een afbeelding van de sleutel in de HUD weergeven om de speler te laten weten dat hij een sleutel 'bezit' die de deuren zou kunnen openen zodra het personage het heeft opgepakt (door het personage over de sleutel in de kamer te bewegen) ).

Beschouw het volgende basisvoorbeeld:

Klik op de demo om hem focus te geven, bestuur het personage met de pijltoetsen van je toetsenbord en voer acties uit met de spatiebalk. (In dit voorbeeld is de actie "open een deur".)

Muren zijn solide blokken die het personage niet doorlaten wanneer ze ermee in botsing komen. Gesloten deuren zijn ook solide.

Om een ​​deur te openen, moet het personage binnen 64 pixels van de deur staan ​​en een sleutel bezitten (dat wil zeggen, de has_key Booleaanse variabele die bepaalt of het teken de sleutel in zijn inventaris heeft, moet dat zijn waar). 

Onder deze omstandigheden, wanneer de speler op de spatiebalk drukt, wordt de status van de betreffende deur gewijzigd. De Booleaanse variabele op slot ingesteld op vals, en de "vaste" status is uitgeschakeld.

In pseudocode zou dit er ongeveer zo uitzien:

Door.Locked = True Door.AnimationFrame = 0 // Het animatieframe dat de deur als vergrendeld weergeeft. Door.Solid = Ingeschakeld // De vaste staat van de deur is ingeschakeld Door.Locked = False Door.AnimationFrame = 1 // De animatie kader dat de deur als geopend weergeeft Door.Solid = Uitgeschakeld // De vaste toestand van de deur is uitgeschakeld Toets Toetsenbord "Spatie" is ingedrukt en Afstand (Teken, Deur) <= 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" 

Herinnering: deze code vertegenwoordigt geen specifieke taal; je zou het in elke gewenste taal moeten kunnen implementeren.

Je kunt ook opmerken dat we controleren wanneer de speler dat doet niet de verwachte sleutel hebben en een feedbackbericht weergeven waarin wordt uitgelegd waarom de deur niet is ontgrendeld. Je kunt cheques gebruiken zoals die het best bij je spel passen - wees je er alleen van bewust dat het altijd leuk is om feedback te geven aan je speler dat zijn actie is geregistreerd en leg uit waarom het niet is voltooid.

Dit is een heel eenvoudige logica van deur en slot en hoe deze te implementeren. In de rest van de zelfstudie bekijken we andere sluitsystemen die varianten zijn van dit basissysteem.

Verschillende sluitsystemen

We hebben het basissysteem gezien waarbij de sleutel een heel deel van de inventaris van het personage is en één sleutel opent alle deuren en kan opnieuw worden gebruikt om verschillende deuren te openen. Laten we hierop voortbouwen.

KeyStack-voorbeeld

In het volgende voorbeeld heeft het personage een stack van sleutels in zijn inventaris. Hoewel er verschillende deurkleuren zijn, is het verschil hier strikt grafisch - het deurobject is logisch hetzelfde als in het basisvoorbeeld, en één type sleutel kan elk van deze openen. Wanneer u echter een sleutel gebruikt om een ​​deur te openen, wordt deze sleutel uit de stapel verwijderd.


Wat codering betreft, is deze wijziging meestal op het niveau van het personage. In plaats van een has_key Booleaanse variabele, u wilt een numerieke variabele die het aantal sleutels bevat dat het karakter "op voorraad" heeft.

Elke keer dat het personage een toets opneemt, voegt u toe 1 naar deze variabele om de stapel omhoog te vertegenwoordigen. Elke keer dat het personage een deur opent, trekt u af 1 van deze variabele om het gebruik van een sleutel te vertegenwoordigen. (In land met videogames worden sleutels vernietigd zodra ze eenmaal worden gebruikt.)

Een andere wijziging is wanneer de speler op de spatiebalk drukt: in plaats van dat te controleren has_key Booleaanse variabele is waar, we willen eigenlijk controleren of de waarde van KeyStack is meer dan nul, zodat we een sleutel kunnen consumeren na het openen van de deur.

In pseudocode ziet dit er ongeveer zo uit:

Deurenmechaniek = hetzelfde als in het bovenstaande basisvoorbeeld. Toetsenbordtoets "Spatie" wordt ingedrukt en Character.KeyStack> 0 en Afstand (Teken, Deur) <= 64 and Door.Locked = True Character.KeyStack = Character.KeyStack - 1 Door.Locked = False 

WhichKey Voorbeeld

In dit nieuwe voorbeeld zullen we een scenario overwegen waarbij voor verschillende typen deuren verschillende soorten sleutels nodig zijn om te worden ontgrendeld.

Hier, net als in het eerste basisvoorbeeld, maken de sleutels deel uit van de inventaris van het personage. We gaan terug naar het gebruik van Booleaanse variabelen om te bepalen of het personage de vereiste sleutels heeft opgepikt. En omdat we verschillende sleutels hebben, zullen we ook verschillende soorten deuren hebben (zwarte deur, rode deur, gouden deur) die ook een gepaste sleutel nodig hebben om ze te openen (zwarte sleutel, rode sleutel, gouden sleutel, respectievelijk ).

De deurobjecten gebruiken verschillende sprites om hun materiaal te tonen en bevatten een numerieke variabele met de naam WhichKey Dit geeft het soort toets aan dat wordt verwacht, evenals de grafische weergave die moet worden weergegeven. De verschillende sleutelwaarden zijn opgenomen als constante variabelen voor een betere leesbaarheid.


In pseudocode:

CONSTANT BLACK_KEY = 0 CONSTANT RED_KEY = 1 CONSTANT GOLD_KEY = 2 Deurmechanismen zijn hetzelfde als in het basisvoorbeeld. Toetsenbordtoets "Spatie" wordt ingedrukt // De deur heeft een zwarte sleutel nodig, maar het karakter heeft er geen Als Door.Locked = True en Door.WhichKey = BLACK_KEY en Character.Has_Black_Key = False en 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

Dit is een variatie op het standaardvoorbeeld dat verschillende soorten sleutels en deuren toestaat en die geen toetsen verbruikt om deuren te openen. Zodra je de sleutel hebt, maakt deze deel uit van je inventaris-deel van de "statistieken" van het personage.

Schakel een voorbeeld

Deze keer moet de speler, in plaats van rechtstreeks op deuren te reageren, een specifieke schakelaar activeren om een ​​specifieke deur te openen of te sluiten.

De deuren hier zijn in wezen hetzelfde als in het basisvoorbeeld. Ze kunnen verschillende afbeeldingen weergeven, maar de logica van het object is nog steeds hetzelfde. Er is echter een toevoeging: we voegen twee numerieke variabelen toe DoorID en SwitchID, die we gebruiken om te weten welke schakelaar aan welke deur is vastgemaakt.

schakelaars zijn een nieuw type objecten die ik heb gekozen om solide te maken (maar dat hoeft niet). Ze bevatten een Booleaanse variabele, geactiveerde, en numerieke variabelen DoorID en SwitchID die, zoals je kunt raden, gebruiken om te bepalen welke schakelaar aan welke deur is vastgemaakt.

Dus wanneer een schakelaar heeft Geactiveerd: waar, de "gekoppelde" deur is ingesteld om te hebben Vergrendeld: fout. Onze actie met de spatiebalk gaat dan plaatsvinden naast a schakelaar, in plaats van naast een deur. Let op de afwezigheid van een sleutel in dit voorbeeld, aangezien de switches fungeren als sleutels:

We zouden gewoon een eenvoudige code kunnen gebruiken die controleert op de schakelaars van de schakelaars in dezelfde kamer (aangezien dit voorbeeld drie deuren en schakelaars in dezelfde ruimte weergeeft), maar later zullen we zien dat we mogelijk schakelaars hebben die op deuren werken die zich in de kamer bevinden een ander ruimte, en dus zal hun actie niet plaatsvinden op het exacte moment dat de speler de schakelaar activeert; het zal later gebeuren, wanneer de nieuwe kamer is geladen.

Om deze reden hebben we dat nodig volharding. Een optie hiervoor is om arrays te gebruiken om gegevens bij te houden zoals de status van de schakelaars (dat wil zeggen, of elke schakelaar is geactiveerd of niet).

In pseudocode:

CONSTANT SWITCH_DOORID = 0 CONSTANT SWITCH_ACTIVATION = 1 // Deze constanten stellen ons in staat om een ​​leesbare herinnering aan de array-coördinaten te behouden 
// Definieer een array // De X-coördinaat van de array komt overeen met de SwitchID-waarde // De Y-0-coördinaat is de DoorID // De Y-1-coördinaat is de activeringsstatus aSwitch (aantal schakelaars, 2) // 2 is het nummer van hoogte (Y), vaak gebaseerd op 0.
Run enige associatie van de SwitchIDs met DoorIDs De deurmonteur is nog steeds hetzelfde als in het basisvoorbeeld. // Weergeven van de juiste schakelaarafbeelding volgens hun activeringsstatus Schakelen. Geactiveerd = Waar Geef het animatieframe weer Switch_ON Switch.Activated = False Geef het animatieframe weer Switch_OFF Toetsenbordtoets "Spatie" wordt ingedrukt en Afstand (teken, schakelaar) <= 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

Voor dit specifieke voorbeeld, waarbij de schakelaars zich in dezelfde ruimte bevinden als de deuren waaraan ze zijn gekoppeld, is het gebruik van de array-techniek overdreven. Als je spel zo is opgezet dat elke schakelaar die op een deur werkt, in dezelfde ruimte wordt geplaatst, ga dan voor de eenvoudiger methode, haal de array weg en controleer op objecten die op je staan alleen scherm.

Plaat-schakelaar voorbeeld

Plaatschakelaars lijken op schakelaars, in die zin dat ze al dan niet geactiveerd zijn, en dat we ze aan deuren kunnen koppelen om ze te vergrendelen of ontgrendelen. Het verschil ligt in hoe een plaatschakelaar wordt geactiveerd, dat is door druk.

In dit voorbeeld van bovenaf wordt de plaatschakelaar geactiveerd telkens wanneer het teken deze overlapt. Je kunt op de spatiebalk drukken om een ​​steen op de plaatschakelaar te laten vallen, en laat deze geactiveerd zelfs als het personage er niet op staat.

De implementatie hiervan is vergelijkbaar met het vorige voorbeeld, met twee kleine wijzigingen:

  • Je moet de bordschakelaar activeren als er een personage of rock bovenop zit.
  • Je moet ervoor zorgen dat de spatiebalk een steen (uit de inventaris) op de bordschakelaar laat vallen.
// Het grootste deel van de implementatie is hetzelfde als in het vorige voorbeeld het object Switch door PlateSwitch-object vervangen // Plate-Switch Mechanic Character OR Rock overlapt NIET PlateSwitch PlateSwitch.Activated = False aSwitch (PlateSwitch.SwitchID, SWITCH_ACTIVATION) = PlateSwitch.Activated Door.DoorID = aSwitch (PlateSwitch.SwitchID, SWITCH.DOORID) // Hiermee kunnen we controleren of we de juiste deurinstance toepassen / gebruiken Door.Locked = True-teken OF Rock overlapt PlateSwitch PlateSwitch.Activated = True aSwitch (PlateSwitch .SwitchID, SWITCH_ACTIVATION) = PlateSwitch.Activated Door.DoorID = aSwitch (PlateSwitch.SwitchID, SWITCH.DOORID) // Hiermee kunnen we controleren of we de juiste instantie van de deur toepassen / selecteren. Door.Locked = False Keyboard Key "Space" is ingedrukt en karakter overlapt PlateSwitch Spawn Rock op PlateSwitch.position 

Mobs Voorbeeld

Een andere mogelijke mechaniek voor een slot is om de speler te verplichten zich te ontdoen van alle vijanden (ook wel mobs genoemd) in een kamer of in een gebied om het ontgrendelen van de deuren te activeren..

Voor dit voorbeeld heb ik een paar gebieden in een enkele kamer gemaakt; elk gebied heeft een deur en meerdere mobs (hoewel die vijanden niet bewegen en geen schade aanrichten).
Elk gebied heeft zijn eigen kleur.

De spatiebalk laat het personage een aantal projectielen afvuren; drie projectielen zullen een menigte doden.

Dit soort mechanica wordt gebruikt in The Legend of Zelda and The Binding of Isaac en draait om een ​​functie die het aantal levende vijanden in de kamer of het gebied controleert. In dit voorbeeld bevat elk gekleurd gebied een telling van de levende mobs, geïnitieerd wanneer de kamer laadt en is vastgemaakt aan de deur. De dood van elke menigte trekt in 1 van deze teller; zodra het daalt naar 0, de deuren Op slot status is veranderd in vals.

// Aan het begin van het spel Voor elk gebied Voor elk overlappend gebied van Mob .AliveMobs = Area.AliveMobs + 1 
Deurmechaniek is hetzelfde als in het basisvoorbeeld
Toetsenbordtoets "Space" wordt ingedrukt Een projectiel uit de positie van het personage spawnen Projectiel botst met Mob Mob.HP = Mob.HP - 1 Projectiel van de vijand vernietigen.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 dit voorbeeld, een Gebied is een gekleurde sprite met een bijbehorende numerieke variabele, AliveMobs, die het aantal mobs telt die het gebied overlappen. Nadat alle mobs in een gebied zijn verslagen, is de bijbehorende deur ontgrendeld (met gebruik van dezelfde monteur als we hebben gezien sinds het basisvoorbeeld).

Navigatie Voorbeeld

Zoals ik al zei in de inleiding, kunnen deuren fungeren als blokkerende obstakels, maar kunnen ze ook worden gebruikt om het personage van de speler van een kamer naar een andere te laten navigeren.

In dit voorbeeld worden deuren standaard ontgrendeld, omdat we meer geïnteresseerd zijn in het navigatieaspect.

De monteur hangt voor een groot deel af van het spel dat je aan het maken bent, evenals de manier waarop je met de datastructuur voor je vloeren omgaat. Ik zal niet ingaan op de details van hoe mijn implementatie hier werkt, want het is heel specifiek voor Construct 2, maar je kunt het vinden in de bronbestanden als je dat wilt.

Conclusie

In dit artikel hebben we gezien hoe deuren tijdelijke obstakels zijn die sleutels vereisen of mechanismen ontgrendelen zoals schakelaars, bordschakelaars of zelfs de dood van mobs. We hebben ook gezien hoe ze kunnen fungeren als "bruggen", waardoor navigatie door verschillende delen van de spelwereld mogelijk wordt.

Ter herinnering: hier zijn enkele mogelijke slotenmechanismen:

  • Eén sleutel voor alle deuren, als onderdeel van de inventaris.
  • Verbruiksleutels: elke keer dat u een deur opent, wordt één sleutel van uw sleutelstapel verwijderd.
  • Verschillende deuren vereisen andere sleutels.
  • Schakelaars of plaatschakelaars, waarbij u niet rechtstreeks op de deur inwerkt om deze te ontgrendelen maar via een afzonderlijk gekoppeld apparaat.
  • Als alle mobs van een gebied worden vermoord, wordt een deur automatisch ontgrendeld.

Als je al die mechanica in een game hebt gemengd, kun je zoiets als dit krijgen:

Hier hebben we een mooie selectie van verschillende deur- en slotmechanieken, waarbij de speler door verschillende kamers moet gaan om de verschillende deuren te ontgrendelen. Voor leerdoeleinden, wil je dit misschien reproduceren in je eigen programmeeromgeving, met behulp van alle voorgaande implementaties die we hebben doorlopen.

Ik hoop dat je dit artikel leuk vond en dat het nuttig voor je was, en ik wil je eraan herinneren dat je de bron kunt vinden voor alle demo's op Github. Je kunt ze openen en bewerken in de gratis versie van Construct 2 (versie r164.2 of hoger).

Referenties

  • Voorbeeldafbeelding: slot ontworpen door João Miranda van het Noun-project