Maak een Megaman-geïnspireerd spel in Construct 2

Ik ga je begeleiden bij het maken van een megaman-geïnspireerde shooter / platformgame. We zullen meer gefocust zijn op de schietaspecten van de gameplay dan op de platforming. In deze tutorial gebruik ik Construct 2 als de tool om het spel te maken, maar ik zal de logica uitleggen met behulp van pseudocode, zodat je deze tutorial in elke taal of engine van jouw keuze kunt volgen.

gerelateerde berichten
  • Bouw een bordspel met twee spelers "Small Tactics" in Construct 2: deel 1
  • Maak een Bomberman-geïnspireerd spel in Construct 2: de speler en het niveau
  • Maak een Match-3-game in Construct 2: The Basics
  • Veelvoorkomende misstappen van nieuwkomers in de Construct 2 Gamedev-gemeenschap

Klik op het spel om het scherp te stellen en gebruik dan de pijltjestoetsen om te bewegen en te springen, en Z schieten.

Om me te concentreren op de gameplay-implementatie, zal ik niet elke functie van Construct 2 toelichten; Ik ga ervan uit dat je de basis kent, zoals het laden van een sprite, een basisbotsing of het spelen van geluiden. Met dat gezegd, laten we beginnen met het maken van de game.


Bereid het kunstwerk voor

Om te beginnen moeten we sprites hebben voor onze game. Gelukkig heeft opengameart ons bedekt met hun prachtige verzameling legale game-art. We hebben vier sets sprites nodig; een held, een vijand en tegels voor platforms.

  • Hero (door Redshrike):
    http://opengameart.org/content/xeon-ultimate-smash-friends
  • Enemy (ook door Redshrike):
    http://opengameart.org/content/fighting-robot-for-ultimate-smash-friends
  • Tegels (door robotaliteit):
    http://opengameart.org/content/prototyping-2d-pixelart-tilesets

Om ze te gebruiken in Construct 2, snijd ik de sprites van de held in afzonderlijke frames met behulp van GIMP.


Basisbeweging

Ik zal het platformgedrag van Construct 2 voor de rest van de tutorial gebruiken, zodat ik me kan concentreren op het schiet- en AI-deel van het spel, dat de hoofdfocus van deze tutorial was.


Construct 2's platformgedrag

Basisbeweging uitgelegd

Als u in een andere taal werkt of als u uw eigen basisgamma van een platformer wilt implementeren in plaats van het ingebouwde gedrag te gebruiken. U hoeft alleen de code in deze sectie te gebruiken als u het ingebouwde gedrag van Construct 2 niet gaat gebruiken.

Om te beginnen moeten we drie manieren bedenken waarop onze held kan bewegen; rechts lopen, links lopen of springen. Voor elk frame werken we de spelsimulatie bij.

aantal bewegingssnelheid = 50; functie-update () moveHero (); 

Om het personage van de speler bij te werken, implementeren we de basisbeweging als volgt:

functie moveHero () // speler drukt op deze toets als (keyDown (KEY_LEFT)) hero.x - = moveSpeed ​​* deltaTime; hero.animate ( "walkLeft");  if (keyDown (KEY_RIGHT)) hero.x + = moveSpeed ​​* deltaTime; hero.animate ( "walkRight");  if (keyDown (KEY_UP)) hero.jump (); hero.animate ( "jump");  // een sleutel die zojuist niet werd ingedrukt als (keyReleased (KEY_LEFT)) hero.animate ("standLeft");  if (keyReleased (KEY_RIGHT)) hero.animate ("standRight"); 

Ik gebruik een andere functie om te springen, omdat springen niet alleen een kwestie is van het veranderen van de y-waarde, maar ook het berekenen van de zwaartekracht. We zullen ook een functie hebben die luistert of een sleutel zojuist is vrijgegeven, om onze heldenanimatie terug te brengen naar een staande animatie.

Laten we het hebben over hoe je de speler laat springen. De held moet weten of hij op dit moment springt of niet, en ook of hij op dit moment aan het vallen is of niet. We zullen dus twee nieuwe variabelen declareren: isJumping and isFalling. Standaard zijn beide onwaar, wat betekent dat de held op een platform staat.

Om een ​​sprong uit te voeren, moeten we eerst controleren of beide waarden onwaar zijn en vervolgens de isJump waar maken.

Functie jump () if (! IsJumping &&! IsFalling) isJumping = True

Om de held in staat te stellen te springen, hebben we een variabele nodig die jumpPower en zwaartekracht wordt genoemd. De standaardwaarde van de jumpPower is -20 en de zwaartekracht is 1. De logica is om de waarde van de sprongkracht toe te voegen aan de Y-positie van de held en de zwaartekracht toe te voegen aan de waarde van de sprongkracht.

We doen dit elke tik. Misschien is dit niet de meest realistische zwaartekrachtfysica die er is, maar games hoeven niet realistisch te zijn, ze moeten gewoon geloofwaardig zijn, daarom hebben sommige spellen supermenselijke sprong en dubbele sprong. De onderstaande code hoort bij de updatefunctie.

If (isJumping || isFalling) hero.y + = hero.jumpPower; hero.jumpPower + = held.zwaartekracht;  // uiteindelijk zal de sprongkracht groter zijn dan nul, en dat betekent dat de held valt als (hero.jumpPower> = 0) isJumping = False; isFalling = True;  // om de val te stoppen, iets te doen wanneer de held het platform overlapt als (hero.isOverlapping (platform1)) // platform1 is het platform waarop onze held kan stappen // stelt de variabele opnieuw in op hun standaardwaarde isJumping = False; isFalling = False; hero.jumpPower = -20;  // en dan is er de vrije val, wanneer de speler over de rand van een platform valt als (! hero.isOverlapping (platform1) && hero.jumpPower < 0 && !isJumping)  // !hero.isOverlapping(platform1) checks whether or not our hero is standing on a platform // and if jumpPower is less than zero and the player is not currently jumping, then that means // he's falling // setting these two values like this will make the player fall. hero.jumpPower = 0; isFalling = true; 

Construct 2's ingebouwde platformgedrag repliceert de bovenstaande voorbeeldcode, die alleen de hulp krijgt die in een andere taal werkt.


De opname implementeren

Nu komt het schietgedeelte van het spel. In de Megaman-serie zijn er drie soorten opnamen: normale opnamen, opgeladen opnamen en energieopnamen door baas.

Normale opnamen zijn voor zichzelf. Opgeladen opnames zijn opnames die het eerst zijn opgeladen voordat ze zijn losgelaten, deze opgeladen opnamen zijn er in twee soorten: half opgeladen en volledig opgeladen. Deze geladen aanvallen zijn sterker dan normale opnamen, waarbij het volledig opgeladen de sterkste wordt.

Baas energie-opnamen zijn opnamen met kracht die de speler heeft verkregen na het verslaan van elke bazen. De schade is hetzelfde als normaal, maar ze hebben speciale eigenschappen die normale opnamen niet hebben.

Nu we het type van elk schot kennen, laten we beginnen ze te maken. Laten we eerst kijken naar de logica achter hoe we elke opname gebruiken. Hier veronderstellen we dat de knop Z op het toetsenbord wordt gebruikt om een ​​opname te maken. We zullen twee verschillende gedragingen implementeren:

  • Normale opnamen: de speler drukt op z en geeft deze vervolgens onmiddellijk vrij (tik op de knop). De kogel wordt één keer per tik geschoten. De animatie wordt gewijzigd om een ​​animatie op te nemen voordat u onmiddellijk overschakelt naar de staande animatie.
  • Opgeladen shots: de speler drukt op Z. De eerste normale kogel wordt neergeschoten. De animatie wordt gewijzigd in de opname voordat direct wordt overgeschakeld naar de staande animatie. Als Z ingedrukt blijft, wordt een laadeffect toegevoegd bovenop de speelanimatie (staand, lopen). Als de Z-knop wordt losgelaten in minder dan 5 seconden na het eerste opladen, wordt een half opgeladen kogel geschoten. Als de Z-knop na 5 seconden wordt losgelaten, wordt een volledig opgeladen kogel gefotografeerd.
  • Baas energieschoten: onze held moet eerst de kogel uitrusten die hij verwierf na het verslaan van een baas. Na het uitrusten, drukt de speler op een andere knop om deze kogel te schieten. Dit kogelgedrag varieert en moet voor elke kogel uniek worden gecodeerd.

Laten we nu gaan coderen. Omdat onze held links en rechts kan schieten, moeten we weten in welke richting hij zich momenteel bevindt. Laten we een nieuwe variabele declareren, genaamd facing, die een stringwaarde opslaat van of de held naar links of rechts kijkt.

String facing = "right"; // in welke richting de held momenteel wordt geconfronteerd met de functie moveHero () // speler drukt op deze toets als (keyDown (KEY_LEFT)) hero.x - = moveSpeed ​​* deltaTime; hero.animate ( "walkLeft"); tegenover = "links";  if (keyDown (KEY_RIGHT)) hero.x + = moveSpeed ​​* deltaTime; hero.animate ( "walkRight"); tegenover = "rechts";  // ... de voortzetting van de functie moveHero () gaat hier functie update () // ... de update-code die we eerder hebben geschreven, gaat hier ... // speler drukt eenmaal op deze toets als (keyPressed (KEY_Z)) if (facing) == "rechts") hero.animate ("Shoot"); // we zullen hier later een functie voor schieten toevoegen else if (facing == "left") hero.animate ("Shoot"); hero.mirrorSprite (); // deze functie draait de sprite horizontaal om if (keyReleased (KEY_Z)) if (facing == "right") hero.animate ("standRight");  else if (facing == "left") hero.animate ("standLeft"); hero.mirrorSprite (); // we moeten dit opnieuw bellen omdat de sprite is gespiegeld // als we de sprite niet opnieuw spiegelen, ziet standLeft eruit als standRight

Verplaats evenementen in Construct 2

Voordat we een kogel schieten, moeten we kijken naar de eigenschappen van de kogel:

  • Kracht: aanval op kracht van de kogel, de schade die het de vijand zal toebrengen
  • Snelheid: hoe snel de kogel gaat
  • Hoek: de opnamehoek, bepaalt in welke richting de kogel gaat.

Deze eigenschappen verschillen voor elk opsommingsteken. In het bijzonder zal de eigenschap power anders zijn. De hoekeigenschap is normaal gesproken slechts een van de twee waarden; of de kogel rechts of links is geschoten, tenzij het een bossenkogel is die in een unieke hoek kan schieten.

Schotvariaties worden later besproken, dus nu zal ik alleen standaardopnames behandelen. Het volgende is het stuk code dat een kogel schiet.

// eerst maken we een functie die een nieuwe bullet creëert. Function shoot (string pathToSprite, number bulletPower, number bulletSpeed, number bulletAngle) myBullet = new Bullet (pathToSprite); myBullet.power = bulletPower; // de bullet-klasse of -object heeft twee privévariabelen die deze verplaatsen in overeenstemming met de hoek // meer uitleg voor deze twee lijnen heeft meer wiskunde nodig, dus ik kies ervoor niet uit te leggen // ik neem aan dat je gekozen engine een manier heeft om een object volgens de hoek ySpeed ​​= Math.sin (bulletAngle) * bulletSpeed; xSpeed ​​= Math.cos (bulletAngle) * bulletSpeed;  // dit is Bullet-klasse 'functie die elk frame wordt genoemd, dit verplaatst de kogel volgens de hoekfunctie moveBullet () x + = xSpeed ​​* deltaTime; y + = ySpeed ​​* deltaTime;  // en dit is de aanpassing van onze vorige update () functiefunctie update () // ... de update code die we eerder hebben geschreven gaat hier ... // speler drukt eenmaal op deze toets als (keyPressed (KEY_Z)) if ( tegenover == "rechts") hero.animate ("Shoot"); hero.shoot ("path / to / sprite.png", 10, 400, 0);  else if (facing == "left") hero.animate ("Shoot"); hero.mirrorSprite (); // deze functie draait de sprite horizontaal hero.shoot ("path / to / sprite.png", 10, 400, 180); // de hoek is 180 zodat de opsommingsteken naar links gaat // ... de voortzetting van de updatecode gaat hier ...

Het bullet-gedrag toevoegen aan bullet-sprites
De schietcode waarbij we een nieuw opsommingsteken maken

Opgeladen shots

Sommige kogels kunnen krachtiger zijn dan andere. Om een ​​geladen opname te maken, hebben we een variabele met de naam chargedTime nodig, die elke seconde zal verhogen met de speler die Z vasthoudt, en zal terugkeren naar nul wanneer de kogel wordt afgevuurd. De wijzigingen in de update-code zijn als volgt:

// speler heeft net de z-toets vrijgegeven als (keyReleased (KEY_Z)) if (chargedTime> 0 && chargedTime <= 5)  if (facing == "right")  hero.animate("Shoot"); hero.shoot("path/to/halfChargedBullet.png", 20, 400, 0); chargedTime = 0;  else if (facing == "left")  hero.animate("Shoot"); hero.mirrorSprite(); // this function flips the sprite horizontally hero.shoot("path/to/halfChargedBullet.png", 20, 400, 180); chargedTime = 0;   else if (chargedTime > 5) if (facing == "right") hero.animate ("Shoot"); hero.shoot ("path / to / fullChargedBullet.png", 40, 400, 0); chargedTime = 0;  else if (facing == "left") hero.animate ("Shoot"); hero.mirrorSprite (); // deze functie draait de sprite horizontaal hero.shoot ("path / to / fullChargedBullet.png", 40, 400, 180); chargedTime = 0;  if (facing == "right") hero.animate ("standRight");  else if (facing == "left") hero.animate ("standLeft"); hero.mirrorSprite (); // we moeten dit opnieuw bellen omdat de sprite is gespiegeld // als we de sprite niet opnieuw spiegelen, zal standLeft eruit zien als standRight // speler drukt op deze toets als (keyDown (KEY_Z)) // dit is de functie die de waarde van chargedTime elke seconde toevoegt // dit keyDown-codeblok wordt elk frame uitgevoerd, wat minder is dan een seconde // uw engine van keuze moet een manier hebben om te bepalen of een seconde is verstreken of niet addChargedTime (); 

Onze held karaktereert nieuwe bewegingen links, rechts en springt volgens onze input en schiet ook kogels, normaal, half geladen of volledig opgeladen.


Vijanden implementeren

We hebben nu een bestuurbare held. Laten we het Xeon noemen omwille van de eenvoud. Hij kan enkele basisbewegingen uitvoeren, zoals wandelen, springen en schieten. Dat is geweldig! Maar wat heb je aan het schieten zonder iets om op te schieten, toch? Daarom gaan we deze keer onze eerste vijand maken.

Laten we de kenmerken van onze vijand ontwerpen voordat we hem gaan coderen.

  • Gezondheid: Hoeveel gezondheid onze vijand heeft, bepaalt hoeveel shots (en welke soort) nodig is om het te vernietigen.

  • Kracht: vijandelijke aanvalskracht, hoeveel schade het onze speler te bieden heeft.

  • ShotAngle: in welke richting de vijand op de kogel schiet, kan deze links of rechts of waar we maar willen.

Dat is zo ongeveer wat we nodig hebben voor onze vijand, laten we nu de vijandklasse / object maken.

De Enemy-klasse / -object is vrijwel hetzelfde als de spelersklasse / het object, behalve dat de vijand niet naar de spelersinvoer luistert. Daarom moeten we de onderdelen waar de held luistert naar spelersinvoer vervangen door vijandelijke AI / logica.


Vijand Aanval AI

Laten we om te beginnen de vijandige basisschiet-AI aan. De vijand zal op de speler schieten als hij de speler ziet.

Om te bepalen of de vijand de speler "ziet", moeten we een variabele definiëren voor het vijandelijke object met de naam facing. Dit is een string die een van de twee waarden opslaat: "left" of "right".

De vijand heeft ook een soort zichtveld nodig, daarom gaan we een andere variabele maken die we bereik noemen. Als de speler binnen dit bereik is, betekent dit dat de vijand de speler "ziet". De pseudocode is als volgt:

function boolean checkSees () if (facing == "left" && hero.x> = enemy.x - range) return true;  if (facing == "right" && hero.x <= enemy.x + range)  return true;  return false; 

checkSees () functie

Misschien heb je iets gemerkt in deze pseudocode: het houdt geen rekening met de positie van de held, dus de vijand schiet nog steeds op de held, zelfs als ze zich op een platform met verschillende hoogtes bevinden.

Voorlopig is dit voldoende, omdat het maken van een gezichtslijnalgoritme buiten het bestek van deze zelfstudie valt. In je eigen spel wil je misschien een Y-tolerantie toevoegen in die functie hierboven om te controleren of de positie van held tussen twee punten ligt die de hoogte van de vijand bepalen.


Vijanden maken schieten

De pseudocode voor vijandelijke schietpartijen is als volgt:

// kan in update () zijn of ergens anders die elke update van de framefunctie heeft uitgevoerd () if (checkSees ()) shoot ("path / to / bulletSprite.png", enemyPower, 400, shotAngle); 

Zoals je kunt zien, is de functie shooting () van de vijand gelijk aan die van de speler. Het neemt het pad van de sprite, richt kracht, snelheid van de kogel en schiethoek als parameters aan.


Vijandelijke beweging AI

Wanneer wisselt de vijand van links naar rechts? Voor onze held gebruiken we spelersinvoer om de richting van onze held te veranderen. Voor onze vijand hebben we twee opties: gebruik een soort timer om elke paar seconden van richting te veranderen terwijl de vijand stil staat of laat de vijand naar een bepaalde plek lopen en dan van richting veranderen en dan naar een andere plek lopen om de richting van het gezicht weer te veranderen.

Deze tweede methode kan worden gebruikt als een patrouillerende AI. Natuurlijk kunnen we de vijand gewoon in dezelfde richting laten lopen en nooit meer teruggaan.

De pseudocode voor de eerste methode is als volgt:

functie switchingAI () // elapsedTime () is een functie die het aantal seconden telt dat is verstreken sinds de waarde is gereset // Ik neem aan dat uw engine van keuze dit soort functionaliteit heeft if (elapsedTime ()> 4.0) if (facing == "left") facing = "right"; shotAngle = 0;  if (facing == "right") facing = "left"; shotAngle = 180;  enemy.mirrorSprite (); // draai ook de sprite horizontaal resetTime (); // reset de tijd die telt in elapsedTime ()

Schakelen van AI-functies

Vijandelijke patrouillerende AI

Om de patrouille-AI te maken, moeten we twee onzichtbare objecten maken die aan beide kanten van de patrouilleweg van de vijand liggen en de vijand op een andere manier laten bewegen als deze tegen hen botst.


Patrouillerende AI

Laten we nu onze pseudocode voor de patrouillerende AI van de vijand schrijven:

functie patrollingAI () if (facing == "right") walkRight (); // dezelfde als die in player-object / class if (collidesWith (rightPatrolBorder)) facing = "left"; enemy.mirrorSprite ();  if (facing == "left") walkLeft (); if (collidesWith (leftPatrolBorder)) facing = "right"; enemy.mirrorSprite (); 

Hierna zal de vijand patrouilleren tussen twee punten zoals we dat willen.

Om in te stellen welke AI de vijand gebruikt, voegen we nog een variabele toe met een stringtype voor onze vijand: vijandelijke AI. Dit zal bepalen welke AI elk frame moet gebruiken, zoals zo:

if (enemyAI == "switching") switchingAI ();  else if (enemyAI == "patrolling") patrollingAI (); 

Natuurlijk kun je meer vijandelijke AI-typen toevoegen als je wilt.


Schotvariatie

Laten we verder gaan over hoe we schotvariaties kunnen maken voor zowel de speler als de vijand. We maken shotvariaties door twee dingen te veranderen: de opnamehoek en het aantal kogels.

Op deze manier kunnen we een eenvoudige bullet-shot of een driekijlige bullet-shot maken. Voordat we dit doen, gaan we een andere variabele maken voor het vijandelijke object / klasse genaamd shotAI, wat een string is. We gebruiken dit in onze checkSees () om te controleren of er een blok is, waar de vijand schiet. De wijzigingen in dat codeblok zullen als volgt zijn:

// kan in update () zijn of ergens anders die elke update van de framefunctie heeft uitgevoerd () if (checkSees ()) if (shotAI == "simple") shoot ("path / to / bulletSprite.png", enemyPower , 400, shot Angle);  if (shotAI == "threeBullets") shootThreeBullets (); 

Natuurlijk is de naam van de AI en het soort schot dat de vijand zou schieten, aan jou, dit is slechts een voorbeeld.

Laten we nu dieper ingaan op wat zich in de shootThreeBullets () -functie bevindt.

Functie shootThreeBullets () if (facing == "right") shoot ("path / to / bulletSprite.png", enemyPower, 400, 0); // deze kogel gaat recht naar de juiste shoot ("path / to / bulletSprite.png", enemyPower, 400, 330); // dit gaat omhoog met 30 graden shoot ("path / to / bulletSprite.png", enemyPower, 400, 30); // dit daalt met 30 graden als (tegenover == "links") shoot ("path / to / bulletSprite.png", enemyPower, 400, 180); // deze kogel gaat recht naar de linker shoot ("path / to / bulletSprite.png", enemyPower, 400, 210); // dit gaat omhoog met 30 graden shoot ("path / to / bulletSprite.png", enemyPower, 400, 150); // dit daalt met 30 graden

Als u niet zeker weet waarom 0 naar rechts gaat en 180 naar links gaat, komt dit doordat de richting van 0 graden recht naar de rechterkant van het scherm gaat, 90 graden naar de onderkant van het scherm gaat, enzovoort totdat het raakt 360 graden. Als je eenmaal weet wat de waarde is, kun je je eigen shotvariatie maken.

We kunnen ook een shotAI-variabele voor de speler maken, maar ik geef de voorkeur aan selectedShot omdat onze speler de opsommingsteken kiest in plaats van geprogrammeerd vanaf het begin. T

hij logica in megaman is elke keer dat megaman een baas verslaat, hij krijgt de macht van die baas als een nieuw schot. Ik ga proberen die logica te reconstrueren. Om dit te doen hebben we een array nodig die de opnamen van de speler bevat, inclusief normale opnamen. De pseudocode is als volgt:

var shotArr = ["normalShot", "boss1", "boss2"]; var shotIndex = 0; var selectedShot = "normalShot"; functie-update () // dit is het codeblok in de updatefunctie van de speler waar de speler een opsommingsteken maakt // deze functie verandert de opsommingsteken die de speler neemt changeBullet (); // speler drukt eenmaal op deze toets als (keyPressed (KEY_Z)) if (facing == "right") hero.animate ("Shoot"); if (selectedShot == "normalShot") hero.shoot ("path / to / sprite.png", 10, 400, 0);  else if (selectedShot == "boss1") // codes toevoegen om het soort shot te schieten dat de speler ontving na het verslaan van boss 1 function changeBullet () // verandert shotIndex op basis van ingedrukte knop if (keyPressed (KEY_E)) shotIndex + = 1;  if (keyPressed (KEY_Q)) shotIndex - = 1;  // fix shotIndex als het buiten de lengte van de array staat (shotIndex == shotArr.length) shotIndex = 0;  if (shotIndex < 0)  shotIndex = shotArr.length -- 1;  selectedShot = shotArr[shotIndex]; 

We moeten twee nieuwe variabelen bijhouden:


Hoe een array in Construct 2 toe te voegen

We zullen nieuwe elementen naar ShotArr pushen wanneer de speler een baas verslaat.


De speler-opsomming opwaarderen

Net als de shootThreeBullet () van de vijand, kun je creatief zijn en je eigen shotvariaties maken. Omdat dit de kogel van de held is, laten we het iets speciaals geven.

Laten we een type foto maken om effectief te zijn tegen een specifieke baas, zodat het meer schade aanricht. Hiertoe maken we een variabele voor het opsommingstoestel met de naam strongAgainst. Dit is een andere tekenreeksvariabele die de naam bevat van de baas waartegen deze bullet werkzaam is. We zullen deze deals meer schade toevoegen als we het baasgedeelte van het spel bespreken.


Gezondheid en dood

Dit is waar alle shotvariaties die we maken er echt toe doen. Dit is waar onze held de vijand beschadigt en doodt, en andersom.

Om te beginnen, laten we een variabele maken voor zowel de held als het vijandelijke object, genaamd gezondheid die een int is, en een andere variabele alleen voor de held met de naam levens. Laten we de pseudocode eens bekijken:

if (bullet.collidesWith (hero)) hero.health - = bullet.power; createExplosion (); // voor nu hebben we geen explosiesprite, dus dit zal als een herinnering werken // controleer of de held dood is als (hero.health <= 0)  hero.lives -= 1; // decreases hero's total number of lives. destroyHero(); 

We zullen dezelfde pseudocode maken voor schadelijke vijanden, zoals zo:

if (bullet.collidesWith (vijand)) enemy.health - = bullet.power; createExplosion ();  if (vijand.health <= 0)  destroyEnemy(); 

Omgaan met kogelbotsingen tussen speler en vijand

De Health Bar GUI

Nu, als ik het daarbij laat, dan zou het niet interessant zijn. Ik maak dus een rechthoek in de linkerbovenhoek van het scherm die fungeert als de gezondheidsbalk van onze held.

De lengte van deze gezondheidsbar gaat veranderen afhankelijk van de huidige gezondheid van onze held. De formule voor het wijzigen van de lengte van de gezondheidsbalk is deze:

// dit staat in de updatefunctie healthBar.width = (hero.health / hero.maxHealth) * 100;

We hebben nog een variabele nodig voor onze held genaamd MaxHealth; de gezondheidswaarde van onze held. Voorlopig kan deze waarde niet worden gewijzigd, maar in de toekomst kunnen we misschien een item maken dat de hoeveelheid maxHealth van de held verhoogt.


Maak de spelwereld

Nu we onze variaties in held, vijand en shot hebben gemaakt, moeten we meerdere niveaus en eindbazen maken.

Als je meerdere niveaus wilt hebben, betekent dit dat de speler op een bepaald punt in de game een of meer ijkpunten zal bereiken die het spel overschakelen van niveau 1-1 naar niveau 1-2 naar niveau 1-3 enzovoort totdat ze de baas bereiken.

Wanneer de speler ergens in niveau 1-2 sterft, hoeft hij of zij niet helemaal terug te spelen vanaf het begin van niveau 1-1. Hoe doe ik dit? Eerst gaan we het niveau halen, ik ga niet veel uitleggen over levelontwerp, maar hier is het voorbeeldniveau in Construct 2.


monster niveau ontwerp

Het beeld in de linkerbovenhoek is de HUD-laag. Hij scrollt en volgt de held wanneer het spel wordt gespeeld.


Deuren en controleposten

Een sprite waar je op moet letten, is de groene sprite in de rechterbovenhoek van het level. Het is het checkpoint in dit level wanneer de held ermee in botsing komt. We zullen het spel op niveau 1-2 zetten.

Om meerdere niveaus te verwerken, hebben we drie variabelen nodig: currentLevel, levelName en nextLevel.

De currentLevel-variabele wordt gemaakt in het held-object / de -klasse. De levelName wordt gemaakt in het game scene (level) -object voor elk niveau. De variabele nextLevel wordt gemaakt in het groene sprite-object.

De logica is als volgt: wanneer de held in botsing komt met de groene sprite (ik noem het greenDoor), veranderen we ons niveau in de gamescene waarin levelName hetzelfde is als de nextLevel-variabele. Nadat we het niveau hebben gewijzigd, veranderen we de waarde van de currentLevel-variabele van hero in dezelfde als levelName van game scene. Dit is de pseudocode:

// dit is binnen de update-functie van het spel als (hero.collidesWith (greenDoor)) changeLevelTo (greenDoor.nextLevel); 

Verander niveaus wanneer we de groene deur aanraken

Een nieuw niveau initialiseren

Hier is de pseudocode voor het omgaan met wanneer het volgende niveau is geladen en klaar om te spelen.

// dit is de functie die wordt geactiveerd wanneer het nieuwe niveau de functie is geladen onStart () hero.currentLevel = scene.LevelName; hero.x = startPos.x; hero.y = startPos.y; 

De speler start posities

Nu we zijn veranderd naar een nieuw niveau, zal ik de oranje sprite achter onze held uitleggen in de levelontwerpafbeelding hierboven. Die oranje sprite is een object dat ik startPos noem. Het wordt gebruikt om de startpositie van elk niveau te markeren.

We verwijzen naar dit object wanneer de held net van niveau veranderde of stierf, zodat we weten waar hem te spawnen.

Hier is de pseudocode voor afhandeling wanneer de held sterft:

// de functie die wordt geactiveerd wanneer de held wordt vernietigd Functie onDestroyed () // herleeft de held als de held nog steeds levens heeft. If (hero.lives> 0) var newHero = new Hero (); newHero.x = startPos.x; newHero.y = startPos.y; 

Nu kunnen we meerdere niveaus hebben en we kunnen de held ook opnieuw laten werpen nadat hij sterft.

Je kunt zoveel levels maken als je wilt, of misschien zelfs twee greenDoor-objecten maken op een niveau dat een van hen teruggaat naar level 1-1 als de speler een puzzel op een verkeerde manier oplost.


Boss Battles

Het is eindelijk tijd om de baas zelf te implementeren. Een boss level maken is net zo eenvoudig als een ander niveau maken dat een baas zal spawnen in plaats van gewone vijanden.

Het moeilijke gedeelte is het creëren van AI voor de baas omdat elke baas een unieke AI zal hebben. Dus om het voor een heleboel bazen gemakkelijk te begrijpen en te dupliceren, zal ik de baas AI afhankelijk maken van de tijd nadat ze zijn uitgezet. Dat betekent dat ze A voor x seconden gaan doen, dan voor y seconden naar B gaan en dan C voor z seconden doen voordat ze teruggaan naar A. De pseudocode ziet er ongeveer zo uit:

// deze code bevindt zich in de update-functie van de baas, dus het wordt elk frame uitgevoerd if (elapsedTime ()> 2.0) // dit als het blok gedurende 3 seconden wordt uitgevoerd, omdat het verschil in tijd met het if-blok hieronder // drie is seconden. BossShot1 (); // shotvariatie die deze keer moet worden uitgevoerd else if (elapsedTime ()> 5.0) bossShot2 ();  else if (elapsedTime ()> 6.0) bossShot3 ();  if (elapsedTime ()> 7.0) // reset de tijd zodat de baas de eerste actie opnieuw uitvoert resetsTime (); 

De definitie van de baas geschoten functons is hieronder. Voel je vrij om het te veranderen zodat het past bij wat je wilt voor een bossgevecht:

function bossShot1 () // een eenvoudige straight shot bossEnemy.shoot ("path / to / bullet / sprite.png", bossPower, 400, shotAngle); // shotAngle is 180 function bossShot2 () // een uit drie richtingen bestaande kogels shot bossEnemy.shoot ("path / to / bullet / sprite.png", bossPower, 400, shotAngle); bossEnemy.shoot ("path / to / bullet / sprite.png", bossPower, 400, shotAngle + 30); bossEnemy.shoot ("path / to / bullet / sprite.png", bossPower, 400, shotAngle - 30);  function bossShot3 () // voor deze, ik ga een cirkel maken, zodat de kogels een cirkel vormen voor (var i = 0; i <= 9; i++)  bossEnemy.shoot("path/to/bullet/sprite.png", bossPower, 400, 36 * i); // change the shotAngle  

Het is aan jou om shotvariaties toe te voegen aan de routine van de baas. De variabelen die worden gebruikt door het vijandelijke object van de baas zijn hetzelfde als het vijandelijke object, behalve dat de bazen geen gebruik maken van de vijandAI- en shotAI-variabelen, omdat beide worden afgehandeld in het if-blok hi