Maak een Space Shooter met PlayCanvas deel 2

Dit is het tweede deel van onze zoektocht om een ​​3D space shooter te maken. In deel een hebben we gekeken naar hoe je een eenvoudig PlayCanvas-spel kunt opzetten, met fysica en botsingen, onze eigen modellen en een camera.

Ter referentie: hier is opnieuw een live demo van ons eindresultaat.

In dit deel gaan we ons concentreren op het dynamisch creëren van entiteiten met scripts (om kogels en asteroïden te spawnen) en hoe dingen zoals een FPS-teller en in-game tekst kunnen worden toegevoegd. Als je het vorige deel al hebt gevolgd en tevreden bent met wat je hebt, kun je daar vanaf beginnen en het volgende gedeelte met minimale instellingen overslaan. Anders, als je helemaal opnieuw moet beginnen:

Minimale installatie

  1. Start een nieuw project.
  2. Verwijder alle objecten in de scène, met uitzondering van Camera, Box en Light.
  3. Plaats zowel het licht als de camera binnenhet vakobject in het hiërarchiepaneel.
  4. Plaats de camera op positie (0,1.5,2) en rotatie (-20,0,0).
  5. Zorg ervoor dat het lichtobject in een positie wordt geplaatst die er goed uitziet (ik plaats het op de doos).
  6. Bevestig een onbuigzaam lichaam component van de doos. Stel het type in op dynamisch. En zet het demping tot 0,95 (beide lineair en hoekig).
  7. Bevestig een botsing component van de doos.
  8. Stel de zwaartekracht naar 0 (uit de scène-instellingen).
  9. Plaats een bol op (0,0,0) alleen maar om deze positie in de ruimte te markeren.
  10. Maak en bevestig dit script aan het vak en bel het Fly.js:
var Fly = pc.createScript ('fly'); Fly.attributes.add ('speed', type: 'number', default: 50); // initialiseer code eenmaal per entiteit genaamd Fly.prototype.initialize = function () ; // update code genaamd elk frame Fly.prototype.update = functie (dt) // Druk op Z om te duwen als (this.app.keyboard.isPressed (pc.KEY_Z)) // Beweeg in de richting van de tegenovergestelde var-kracht = this.entity.forward.clone (). schaal (this.speed); this.entity.rigidbody.applyForce (kracht);  // Roteer omhoog / omlaag / links / rechts als (this.app.keyboard.isPressed (pc.KEY_UP)) var force_up = this.entity.right.clone (). Schaal (1); this.entity.rigidbody.applyTorque (force_up);  if (this.app.keyboard.isPressed (pc.KEY_DOWN)) var force_down = this.entity.right.clone (). scale (-1); this.entity.rigidbody.applyTorque (force_down);  if (this.app.keyboard.isPressed (pc.KEY_RIGHT)) // Roteer naar rechts var force_right = this.entity.up.clone (). scale (-1); this.entity.rigidbody.applyTorque (force_right);  if (this.app.keyboard.isPressed (pc.KEY_LEFT)) var force_left = this.entity.up.clone (). schaal (1); this.entity.rigidbody.applyTorque (force_left); ; // swap-methode vereist voor hot-reloading van scripts // erven uw scriptstatus hier in Fly.prototype.swap = function (oud) ; // lees voor meer informatie over de anatomie van het script: // http://developer.playcanvas.com/en/user-manual/scripting/

Test dat alles werkte. Je zou met Z moeten kunnen vliegen om de stuwkracht en de pijltjestoetsen te draaien!

8. Paaiperaïden

Het dynamisch creëren van objecten is cruciaal voor bijna elk type game. In de demo die ik heb gemaakt, spawn ik twee soorten asteroïden. De eerste soort zweeft gewoon rond en fungeert als passieve obstakels. Ze respawnen wanneer ze te ver weg zijn om een ​​consistent dicht asteroïde veld rondom de speler te creëren. De tweede soort spawnen van verder weg en bewegen naar de speler (om een ​​gevoel van gevaar te creëren, zelfs als de speler niet beweegt). 

We hebben drie dingen nodig om onze asteroïden te spawnen:

  1. Een AsteroidModel entiteit van waaruit alle andere planetoïden klonen.
  2. Een AsteroidSpawner script gehecht aan de wortel object dat als onze fabriek / cloner zal fungeren.
  3. Een Asteroïde script om het gedrag van elke asteroïde te definiëren. 

Een asteroïde-model maken 

Maak een nieuwe entiteit uit een model naar keuze. Dit kan iets uit de PlayCanvas-winkel zijn, of iets van BlendSwap, of gewoon een basisvorm. (Als u uw eigen modellen gebruikt, is het een goede gewoonte om deze eerst in Blender te openen om het aantal gebruikte gezichten te controleren en indien nodig te optimaliseren.)

Geef het een geschikte botsingsvorm en een onbuigzaam lichaamsdeel (zorg ervoor dat het dynamisch is). Als u tevreden bent, schakelt u het selectievakje uit Ingeschakeld doos:

Wanneer u een object als dit uitschakelt, komt dit overeen met het verwijderen van het object uit de wereld voor zover het de speler betreft. Dit is handig voor het tijdelijk verwijderen van objecten, of in ons geval, voor het houden van een object met al zijn eigenschappen, maar niet om het in het spel te laten verschijnen.

Het asteroïde spawnerscript maken 

Maak een nieuw script met de naam AsteroidSpawner.js en koppel dit aan de Wortel object in de hiërarchie. (Merk op dat de Root gewoon een normaal object is waaraan eventuele componenten kunnen zijn gekoppeld, net als de camera.)

Open nu het script dat u zojuist hebt gemaakt. 

De algemene manier om een ​​entiteit te klonen en deze via script aan de wereld toe te voegen, ziet er als volgt uit:

// Maak de kloon var newEntity = oldEntity.clone (); // Voeg het toe aan het root-object this.app.root.addChild (newEntity); // Geef het een nieuwe naam, anders wordt het ook oldEntity's naam newEntity.name = "ClonedEntity"; // Schakel het in, ervan uitgaande dat oldEntity is ingesteld op uitgeschakeld newEntity.enabled = true;

Dit is hoe u een object zou klonen als u al een "oldEntity" -object had. Dit laat één vraag onbeantwoord: Hoe krijgen we toegang tot het AsteroidModel dat we hebben gemaakt? 

Er zijn twee manieren om dit te doen. De flexibelere manier is om een ​​scriptkenmerk te maken dat bijhoudt welke entiteit moet worden gekloond, zodat u eenvoudig modellen kunt wisselen zonder het script aan te raken. (Dit is precies hoe we de camera hebben bekekenAan het script in stap 7.)

De andere manier is om de functie findByName te gebruiken. Je kunt deze methode op elke entiteit noemen om een ​​van zijn kinderen te vinden. We kunnen het dus op het hoofdobject noemen:

var oldEntity = this.app.root.findByName ("AsteroidModel");

En dit zal onze code van boven vervolledigen. Het volledige AsteroidSpawner-script ziet er nu als volgt uit:

var AsteroidSpawner = pc.createScript ('asteroidSpawner'); // initialiseer code eenmaal per entiteit genaamd AsteroidSpawner.prototype.initialize = function () var oldEntity = this.app.root.findByName ("AsteroidModel"); // Maak de kloon var newEntity = oldEntity.clone (); // Voeg het toe aan het root-object this.app.root.addChild (newEntity); // Geef het een nieuwe naam, anders wordt het ook oldEntity's naam newEntity.name = "ClonedEntity"; // Schakel het in, ervan uitgaande dat oldEntity is ingesteld op uitgeschakeld newEntity.enabled = true; // Zet zijn positie newEntity.rigidbody.teleport (nieuwe pc.Vec3 (0,0,1)); ; // update code genaamd elk frame AsteroidSpawner.prototype.update = function (dt) ; 

Test dat dit werkte door te lanceren en te kijken of uw asteroïdenmodel bestaat.

Opmerking: ik heb gebruikt newEntity.rigidbody.teleport in plaats van newEntity.setPosition. Als een entiteit een rigide lichaam heeft, zal het starre lichaam de positie en rotatie van de entiteit vervangen, dus onthoud om deze eigenschappen op de rigide en niet op de entiteit zelf in te stellen.

Probeer voordat je verder gaat tien of meer asteroïden rond de speler te laten spawnen, willekeurig of op een bepaalde systematische manier (misschien zelfs in een cirkel?). Het zou helpen om al je spawning-code in een functie te plaatsen, zodat het er ongeveer zo uit zou zien:

AsteroidSpawner.prototype.initialize = function () this.spawn (0,0,0); this.spawn (1,0,0); this.spawn (1,1,0); // enz… ; AsteroidSpawner.prototype.spawn = functie (x, y, z) // Paaicode hier ...

Het asteroïdescript maken

Je zou comfortabel moeten zijn nu nieuwe scripts toevoegen. Maak een nieuw script (genaamd Asteroid.js) en koppel het aan het AsteroidModel. Omdat al onze uitgelekte asteroïden klonen zijn, hebben ze allemaal hetzelfde script als bijlage. 

Als we veel asteroïden maken, is het een goed idee om ervoor te zorgen dat ze worden vernietigd als we ze niet langer nodig hebben of als ze ver genoeg weg zijn. Dit is een manier om dit te doen:

Asteroid.prototype.update = function (dt) // Verkrijg de speler var player = this.app.root.findByName ("Ship"); // Vervang "Schip" door wat de naam van je speler ook is // Kloon de positie van de asteroïde var distance = this.entity.getPosition (). Clone (); // Trek de positie van de speler af van de positie van deze asteroïde distance.sub (player.getPosition ()); // Haal de lengte van deze vector op als (distance.length ()> 10) // Een willekeurige drempel this.entity.destroy (); ;

Debugging Tip: Als u iets wilt afdrukken, kunt u altijd de browserconsole gebruiken alsof dit een normale JavaScript-app is. Dus je zou zoiets kunnen doen console.log (distance.toString ()); om de afstandsvector af te drukken, en deze verschijnt in de console.

Controleer voordat u doorgaat of de asteroïde wel verdwijnt wanneer u zich ervan verwijdert.

9. Kogels schieten

Paaiende kogels hebben ongeveer hetzelfde idee als spawning asteroïden, met één nieuw concept: We willen detecteren wanneer de kogel iets raakt en verwijderen. Om ons bullet-systeem te maken, hebben we het volgende nodig:

  1. Een kogelmodel om te klonen. 
  2. Een Shoot.js-script voor spawning-opsommingstekens wanneer u op X drukt. 
  3. Een Bullet.js-script voor het definiëren van het gedrag van elke bullet. 

Een Bullet-model maken

Je kunt elke vorm gebruiken voor je kogel. Ik gebruikte een capsule om een ​​idee te hebben in welke richting de kogel stond. Maak net zoals eerder je entiteit, schaal hem naar beneden en geef hem een ​​dynamisch, strak lichaam en een geschikte aanvaringsdoos. Geef het de naam "Bullet", zodat het gemakkelijk te vinden is.

Als je klaar bent, schakel je het uit (met het selectievakje Ingeschakeld).

Een opnamescript maken

Maak een nieuw script en koppel het aan je spelersschip. Deze keer gaan we een attribuut gebruiken om een ​​verwijzing naar onze bullet-entiteit te krijgen:

Shoot.attributes.add ('bullet', type: 'entity');

Ga terug naar de editor en klik op "parseren" om het nieuwe attribuut weer te geven en selecteer de opsommingsteken die je hebt gemaakt. 

Nu in de updatefunctie willen we:

  1. Klop het en voeg het toe aan de wereld.
  2. Voer een kracht uit in de richting waarin de speler kijkt. 
  3. Plaats deze voor de speler.

U bent al kennisgemaakt met al deze concepten. Je hebt gezien hoe asteroïden moeten worden gekloond, hoe je een kracht moet toepassen in een richting om het schip te laten bewegen en hoe je dingen positioneert. Ik zal de implementatie van dit onderdeel als een uitdaging verlaten. (Maar als je vastloopt, kun je altijd gaan kijken hoe ik mijn eigen Shoot.js-script in mijn project heb geïmplementeerd).

Hier zijn enkele tips die u hoofdpijn kunnen besparen:

  1. Gebruik keyboard.wasPressed in plaats van keyboard.isPressed. Bij het detecteren wanneer op de X-toets wordt gedrukt om een ​​schot af te vuren, is het eerste een handige manier om het alleen te laten vuren als je drukt in plaats van te schieten zolang als je de knop ingedrukt houdt.
  2. Gebruik rotateLocal in plaats van een absolute rotatie in te stellen. Om ervoor te zorgen dat de kogel altijd evenwijdig aan het schip spawnt, was het lastig om de hoeken correct te berekenen. Een veel eenvoudigere manier is om eenvoudig de rotatie van de kogel in de rotatie van het schip in te stellen en vervolgens de kogel in de lokale ruimte 90 graden op de X-as te draaien.

Het Bullet Behavior-script maken

Op dit punt moeten je kogels uitzetten, op de asteroïden slaan en gewoon weer in de lege ruimte stuiteren. Het aantal kogels kan snel overweldigend worden, en weten hoe botsingen moeten worden gedetecteerd, is nuttig voor allerlei dingen. (Het kan u bijvoorbeeld zijn opgevallen dat u objecten kunt maken die alleen een botscomponent hebben maar geen strak lichaam, deze zouden als triggers werken maar zouden niet fysiek reageren.) 

Maak een nieuw script met de naam Bullet en koppel het aan je Bullet-model dat wordt gekloond. PlayCanvas heeft drie soorten contactgebeurtenissen. We gaan luisteren collisionend, die vuurt wanneer de objecten scheiden (anders wordt de kogel vernietigd voordat de asteroïde een kans heeft om te reageren).  

Als u wilt luisteren naar een contactgebeurtenis, typt u dit in uw init-functie:

this.entity.collision.on ('collisionend', this.onCollisionEnd, this);

En creëer vervolgens de luisteraar zelf:

Bullet.prototype.onCollisionEnd = function (result) // Vernietig de kogel als deze een asteroïde raakt als (result.name == "Asteroid") this.entity.destroy (); ;

Dit is waar de naam die je hebt gegeven aan je asteroïden toen je ze ontsproot relevant wordt. We willen dat de kogel alleen wordt vernietigd als deze botst met een asteroïde. resultaat is de entiteit waarmee het botste.

Als alternatief kun je die cheque verwijderen en alleen laten vernietigen bij een botsing met iets.

Het is waar dat er geen andere objecten in de wereld zijn om mee in botsing te komen, maar ik had al vroeg problemen met de speler die de botsing van de kogel voor één frame veroorzaakte en die verdween voordat deze kon worden gelanceerd. Als je ingewikkeldere botsingen nodig hebt, ondersteunt PlayCanvas botsingsgroepen en maskers, maar het is niet erg goed gedocumenteerd op het moment van schrijven.

10. Een FPS-meter toevoegen

Op dit moment zijn we in wezen klaar met de game zelf. Natuurlijk zijn er een heleboel kleine Poolse dingen die ik heb toegevoegd aan de definitieve demo, maar er is niets wat je niet kunt doen met wat je tot nu toe hebt geleerd. 

Ik wilde je laten zien hoe je een FPS-meter kunt maken (hoewel PlayCanvas al een profiler heeft, je kunt de muisaanwijzer over de afspeelknop houden en het profiler-vakje bekijken) omdat het een goed voorbeeld is van het toevoegen van een DOM-element buiten de PlayCanvas-engine. 

We gaan deze gladde FPSMeter-bibliotheek gebruiken. Het eerste dat u moet doen, is naar de website van de bibliotheek gaan en de verknipte productieversie downloaden.

Ga terug naar je PlayCanvas-editor, maak een nieuw script en kopieer de code fpsMeter.min.js. Voeg dit script toe aan het root-object.

Nu de bibliotheek is geladen, maakt u een nieuw script dat de bibliotheek initialiseert en gebruikt. Noem het meter.js en uit het gebruik van de website van de bibliotheek hebben we:

var Meter = pc.createScript ('meter'); Meter.prototype.initialize = function () this.meter = new FPSMeter (document.body, grafiek: 1, heat: 1); ; Meter.prototype.update = function (dt) this.meter.tick (); ; 

Voeg ook het meterscript toe aan het root-object en start. Je zou de FPS-teller in de linkerbovenhoek van het scherm moeten zien!

11. Tekst toevoegen

Laten we tenslotte wat tekst toevoegen in onze wereld. Deze is een beetje betrokken omdat er verschillende manieren zijn om het te doen. Als u alleen een statische gebruikersinterface wilt toevoegen, kunt u dit in de eerste plaats doen met de DOM door uw UI-elementen bovenop het canvaselement van PlayCanvas te leggen. Een andere methode is om SVG's te gebruiken. Dit bericht bespreekt enkele van deze verschillende manieren.

Omdat dit allemaal standaardmanieren zijn voor het verwerken van tekst op internet, heb ik ervoor gekozen om te kijken hoe tekst kan worden gemaakt die zich in de ruimte van de gamewereld bevindt. Zie het dus als een tekst die een teken zou zijn in de omgeving of een voorwerp in het spel.

De manier waarop we dit doen is door een materiaal voor elk stuk tekst dat we willen weergeven. We maken vervolgens een onzichtbaar canvas dat we de tekst renderen naar de vertrouwde canvas fillText-methode. Eindelijk, wij maak het canvas op het materiaal om het in het spel te laten verschijnen.

Merk op dat deze methode kan worden gebruikt voor meer dan alleen tekst. Je zou dynamisch texturen kunnen tekenen of iets kunnen doen wat een canvas kan doen.

Maak het tekstmateriaal

Maak een nieuw materiaal en noem het zoiets als "TextMaterial". Zet de diffuse kleur op zwart, want onze tekst zal wit zijn.

Maak een vlakke entiteit en voeg dit materiaal eraan toe.

Maak het tekstscript

U vindt het volledige script text.js in deze essentie:

https://gist.github.com/OmarShehata/e016dc219da36726e65cedb4ab9084bd

U kunt zien hoe de structuur wordt ingesteld om het canvas als een bron te gebruiken, met name aan de lijn: this.texture.setSource (this.canvas);

Maak dit script en koppel het aan je vliegtuig. Merk op hoe het twee attributen creëert: tekst en lettertypegrootte. Op deze manier zou je hetzelfde script kunnen gebruiken voor elk tekstobject in je game.

Start de simulatie en u zou ergens de grote "Hello World" -tekst moeten zien. Als je het niet ziet, zorg er dan voor dat a) het een lichtbron in de buurt heeft en b) je naar de juiste kant kijkt. De tekst wordt niet weergegeven als u deze van achteren bekijkt. (Het helpt ook om een ​​fysiek object in de buurt van het vlak te plaatsen om het eerst te lokaliseren.)

12. Publiceren

Zodra je je geweldige prototype hebt samengesteld, kun je klikken op het PlayCanvas-pictogram in de linkerbovenhoek van het scherm en "Publiceren" selecteren. Hier kunt u nieuwe builds publiceren die op PlayCanvas worden gehost en deze met de wereld delen!

Conclusie

Dat is het voor deze demo. Er is nog veel meer te ontdekken in PlayCanvas, maar hopelijk krijgt dit overzicht je voldoende vertrouwd met de basisbeginselen om je eigen games te bouwen. Het is echt een mooie motor waarvan ik denk dat meer mensen het moeten gebruiken. Veel van wat er mee is gemaakt, zijn technische demo's in plaats van volledige games, maar er is geen reden waarom je er niets geweldigs mee kunt bouwen en publiceren.

Een eigenschap waar ik niet echt over sprak, maar misschien wel duidelijk was, is dat de editor van PlayCanvas je toestaat om je spel in realtime bij te werken. Dit geldt voor het ontwerp, in die zin dat u dingen in de editor kunt verplaatsen en dat ze in het startvenster worden bijgewerkt als u deze hebt geopend, evenals voor code, met de hot-reloading.

Eindelijk, terwijl de editor echt handig is, kan alles wat je ermee kunt doen met pure code worden gedaan. Dus als je PlayCanvas op een budget moet gebruiken, is een goede verwijzing naar gebruik de voorbeeldenmap op GitHub. (Een goed beginpunt is dit eenvoudige voorbeeld van een draaiende kubus.)

Als er iets verwarrends is, laat het me dan weten in de comments! Of gewoon als je iets cools hebt gebouwd en wilt delen, of een eenvoudigere manier hebt gevonden om iets te doen, zou ik het graag willen zien!