Bygg ditt første spill med HTML5

HTML5 vokser opp raskere enn noen kunne ha forestilt seg. Kraftige og profesjonelle løsninger utvikles allerede? selv i spillverdenen! Sjekk ut hundrevis av HMHTML5-spill på Envato Market.

I dag lager du ditt første spill ved hjelp av Box2D og HTML5 lerret stikkord.


Hva er Box2D?

Box2D er en åpen kildekode og populær motor som simulerer 2D fysikk for å lage spill og applikasjoner. Primært skrevet i C ++, det har blitt konvertert til mange språk av samfunnsansatte.

Med samme metoder og objekter har du muligheten til å lage spillets fysikk på mange språk, for eksempel Mål C (iPhone / iPad), ActionScript 3.0 (Web), HTML 5 (Web), etc.


Trinn 1 - Sette opp prosjektet ditt

For å begynne å utvikle din demo, last ned Box2D-motoren for HTML5 her. Deretter oppretter du en ny HTML-fil med følgende struktur (kopier js og lib-kataloger fra box2d-js-prosjektet til spillmappen din).

Nå må du sette inn de nødvendige filene for å kjøre box2D i HTML-filen din:

                                                                  

Ja, det er et stort antall HTTP-forespørsler!

Vær oppmerksom på at for distribusjon anbefales det sterkt at du sammenkaller alle disse ressursene til en manus fil.

Deretter oppretter du to skript inni / Js / mappe, kalt "box2dutils.js" og "game.js".

  • box2dutils.js - det er en kopi og lim inn fra noen demoer som følger med box2dlib, og er viktig for tegning av funksjoner (jeg vil også forklare noen viktige deler her).
  • game.js - selve spillet; Det er her vi lager plattformene, spilleren, bruker tastaturets interaksjoner, osv.

Kopier og lim inn følgende kode i box2dutils.js. Ikke bekymre deg! Jeg skal forklare det litt etter litt!

funksjon drawWorld (verden, kontekst) for (var j = world.m_jointList; j; j = j.m_next) drawJoint (j, kontekst);  for (var b = world.m_bodyList; b; b = b.m_next) for (var s = b.GetShapeList (); s! = null; s = s.GetNext ()) drawShape (s, kontekst) ;  funksjon drawJoint (felles, kontekst) var b1 = joint.m_body1; var b2 = joint.m_body2; var x1 = b1.m_position; var x2 = b2.m_position; var p1 = joint.GetAnchor1 (); var p2 = joint.GetAnchor2 (); context.strokeStyle = '# 00eeee'; context.beginPath (); bytt (joint.m_type) tilfelle b2Joint.e_distanceJoint: context.moveTo (p1.x, p1.y); context.lineTo (p2.x, p2.y); gå i stykker; tilfelle b2Joint.e_pulleyJoint: // TODO pause; standard: hvis (b1 == world.m_groundBody) context.moveTo (p1.x, p1.y); context.lineTo (x2.x, x2.y);  annet hvis (b2 == world.m_groundBody) context.moveTo (p1.x, p1.y); context.lineTo (x1.x, x1.y);  ellers context.moveTo (x1.x, x1.y); context.lineTo (p1.x, p1.y); context.lineTo (x2.x, x2.y); context.lineTo (p2.x, p2.y);  gå i stykker;  context.stroke ();  funksjon drawShape (form, kontekst) context.strokeStyle = '# 000000'; context.beginPath (); bryter (shape.m_type) tilfelle b2Shape.e_circleShape: varcirkel = form; var pos = circle.m_position; var r = sirkel.m_radius; var segmenter = 16,0; var theta = 0,0; var dtheta = 2,0 * Math.PI / segmenter; // tegne sirkel context.moveTo (pos.x + r, pos.y); for (var i = 0; i < segments; i++)  var d = new b2Vec2(r * Math.cos(theta), r * Math.sin(theta)); var v = b2Math.AddVV(pos, d); context.lineTo(v.x, v.y); theta += dtheta;  context.lineTo(pos.x + r, pos.y); // draw radius context.moveTo(pos.x, pos.y); var ax = circle.m_R.col1; var pos2 = new b2Vec2(pos.x + r * ax.x, pos.y + r * ax.y); context.lineTo(pos2.x, pos2.y);  break; case b2Shape.e_polyShape:  var poly = shape; var tV = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[0])); context.moveTo(tV.x, tV.y); for (var i = 0; i < poly.m_vertexCount; i++)  var v = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[i])); context.lineTo(v.x, v.y);  context.lineTo(tV.x, tV.y);  break;  context.stroke();  function createWorld()  var worldAABB = new b2AABB(); worldAABB.minVertex.Set(-1000, -1000); worldAABB.maxVertex.Set(1000, 1000); var gravity = new b2Vec2(0, 300); var doSleep = true; var world = new b2World(worldAABB, gravity, doSleep); return world;  function createGround(world)  var groundSd = new b2BoxDef(); groundSd.extents.Set(1000, 50); groundSd.restitution = 0.2; var groundBd = new b2BodyDef(); groundBd.AddShape(groundSd); groundBd.position.Set(-500, 340); return world.CreateBody(groundBd)  function createBall(world, x, y)  var ballSd = new b2CircleDef(); ballSd.density = 1.0; ballSd.radius = 20; ballSd.restitution = 1.0; ballSd.friction = 0; var ballBd = new b2BodyDef(); ballBd.AddShape(ballSd); ballBd.position.Set(x,y); return world.CreateBody(ballBd);  function createBox(world, x, y, width, height, fixed, userData)  if (typeof(fixed) == 'undefined') fixed = true; var boxSd = new b2BoxDef(); if (!fixed) boxSd.density = 1.0; boxSd.userData = userData; boxSd.extents.Set(width, height); var boxBd = new b2BodyDef(); boxBd.AddShape(boxSd); boxBd.position.Set(x,y); return world.CreateBody(boxBd) 

Trinn 2 - Utvikle spillet

Åpne index.html fil som du tidligere har opprettet, og legg til en lerret element (600x400) innenfor kropp element. Dette er hvor vi skal jobbe med HTML5 tegning API:

Også, mens du er her, referanse game.js og box2dutils.js.

 

Det vil gjøre det for HTML! La oss jobbe med det morsomme JavaScript nå!

Åpen game.js, og sett inn koden nedenfor:

// noen variabler som vi skal bruke i denne demoen var initId = 0; var spiller = funksjon () this.object = null; this.canJump = false; ; var verden; var ctx; var canvasWidth; var canvasheight; var keys = []; // HTML5 onLoad-hendelse Event.observe (vindu, last), funksjon () world = createWorld (); // box2DWorld ctx = $ ('spill'). GetContext ('2d'); // 2 var canvasElm = $ ('spill'); canvasWidth = parseInt (canvasElm.width); canvasHeight = parseInt (canvasElm.height); initGame (); // 3 trinn (); // 4 // 5 window.addEventListener ('keydown' handleKeyDown, true); window.addEventListener ('keyup', handleKeyUp, true););

Box2DWorld - det er derfor vi er her

Ok, la oss finne ut hva denne delen av koden gjør!

Box2DWorld er en av klassene som er tilgjengelig, via kjernen i box2d. Funksjonen er enkel: kombinere alt inn i en klasse. I box2DWorld har du kroppsdefinisjonen og kollisjonssjefen for spillet ditt eller programmet.

Hold game.js og box2dutils.js filer åpne, og søk etter createWorld () fungere innenfor box2dutils.js.

funksjon createWorld () // her lager vi våre verdensinnstillinger for kollisjoner var worldAABB = new b2AABB (); worldAABB.minVertex.Set (-1000, -1000); worldAABB.maxVertex.Set (1000, 1000); // sett tyngdekraften vektor var tyngdekraften = ny b2Vec2 (0, 300); var doSleep = true; // init vår verden og returnere sin verdi var world = new b2World (worldAABB, gravity, doSleep); returnere verden; 

Det er ganske enkelt å lage box2DWorld.


Tilbake til game.js

Se de kommenterte tallene i de to blokkene med kode ovenfor. På nummer to, henter vi lerret elementets kontekst ved hjelp av selector API (ser ut som jQuery eller MooTools selectors, ikke sant?). På nummer tre har vi en ny interessant funksjon: initGame (). Det er her vi skaper naturen.

Kopier og lim inn koden under i game.js, og så vurderer vi det sammen.

funksjon initGame () // lage 2 store plattformer createBox (verden, 3, 230, 60, 180, true, 'ground'); createBox (world, 560, 360, 50, 50, true, 'ground'); // lage små plattformer for (var i = 0; i < 5; i++) createBox(world, 150+(80*i), 360, 5, 40+(i*15), true, 'ground');  // create player ball var ballSd = new b2CircleDef(); ballSd.density = 0.1; ballSd.radius = 12; ballSd.restitution = 0.5; ballSd.friction = 1; ballSd.userData = 'player'; var ballBd = new b2BodyDef(); ballBd.linearDamping = .03; ballBd.allowSleep = false; ballBd.AddShape(ballSd); ballBd.position.Set(20,0); player.object = world.CreateBody(ballBd);  

Innsiden box2dutils.js, Vi har opprettet en funksjon, kalt createBox. Dette skaper en statisk rektangel kropp.

funksjon createBox (verden, x, y, bredde, høyde, fast, userData) hvis (typeof (fast) == 'undefined') fast = true; // 1 var boxSd = ny b2BoxDef (); hvis (! fast) boxSd.density = 1.0; // 2 boxSd.userData = userData; // 3 boxSd.extents.Set (bredde, høyde); // 4 var boxBd = new b2BodyDef (); boxBd.AddShape (boxSd); // 5 boxBd.position.Set (x, y); // 6 return world.CreateBody (boxBd)

Box2DBody

EN Box2DBody har noen unike egenskaper:

  • Det kan være statisk (ikke påvirket av kollisjoner), kinematisk (det påvirkes ikke av kollisjoner, men det kan flyttes med musen, for eksempel), eller dynamisk (samhandler med alt)
  • Må ha en formdefinisjon, og skal angi hvordan objektet vises
  • Kan ha mer enn en fixtur, som indikerer hvordan objektet vil samhandle med kollisjoner
  • Dens posisjon er satt av midten av objektet, ikke den venstre toppkanten som mange andre motorer gjør.

Gjennomgang av koden:

  1. Her oppretter vi en formdefinisjon som vil være et firkant eller rektangel, og oppsett dens tetthet (hvor ofte det skal flyttes eller roteres av krefter).
  2. Vi konfigurerer brukerdata, Vanligvis installerer du grafikkobjekter her, men i dette eksempelet installerer jeg bare strenger som vil være identifikatoren for typen objekt for kollisjoner. Denne parameteren påvirker ikke fysikkalgoritmer.
  3. Oppsett halvparten av størrelsen på boksen min (det er en linje fra posisjonspunktet eller midtpunktet til objektet til et hjørne)
  4. Vi lager kroppsdefinisjonen, og legger til den i boksformedefinisjonen.
  5. Oppsett posisjonen.
  6. Opprett kroppen i verden og returner verdien.

Opprette spillerklokkehuset

Jeg har kodet spilleren (ball) direkte i game.js fil. Det følger samme rekkefølge med å lage bokser, men denne gangen er det en ball.

var ballSd = ny b2CircleDef (); ballSd.density = 0,1; ballSd.radius = 12; ballSd.restitution = 0.5; ballSd.friction = 1; ballSd.userData = 'player'; var ballBd = ny b2BodyDef (); ballBd.linearDamping = .03; ballBd.allowSleep = false; ballBd.AddShape (ballSd); ballBd.position.Set (20,0); player.object = world.CreateBody (ballBd);

Så hvordan lager vi en kropp, trinnvis?

  1. Opprett form, fixtur og sensordefinisjon
  2. Opprett kroppsdefinisjonen
  3. Legg i kroppen din form, armaturer eller sensorer (ikke forklart i denne artikkelen)
  4. Lag kroppen i verden

Box2DCircle

Som jeg nevnte tidligere, følger dette samme opprettelsesprosess med en boks, men nå må du sette noen nye parametere.

  • radius - Dette er lengden på en linje fra sirkelens senter til et hvilket som helst punkt på kanten.
  • restitusjon - Hvordan ballen vil miste, eller få kraft når den kolliderer med andre kroppen.
  • friksjon - Hvordan ballen vil rulle.

Box2DBody - Flere eiendommer

  • demping brukes til å redusere kroppens hastighet - det er vinkeldemping og lineær demping.
  • sove i boks2D kan kropper sove for å løse ytelsesproblemer. For eksempel, la oss anta at du utvikler et plattformspill, og nivået er definert av en 6000x400 skjerm. Hvorfor trenger du å utføre fysikk for objekter som er utenfor skjermen? Du gjør det ikke; det er poenget! Så det riktige valget er å sette dem i dvale, og forbedre spillets ytelse.

Vi har allerede skapt vår verden; Du kan teste koden du har så langt. Du ser at spilleren faller over vestplattformen.

Nå, hvis du prøvde å kjøre demoen, bør du lure på hvorfor er siden så ufruktbar som hvitt papir?

Husk alltid: Box2D gjengir ikke; det beregner bare fysikk.


Trinn 3 - Renderingstid

Deretter la oss gjengi box2DWorld.

Åpne din game.js script, og legg til følgende kode:

funksjonstrinn () var stepping = false; var timeStep = 1,0 / 60; var iterasjon = 1; // 1 world.Step (timeStep, iterasjon); // 2 ctx.clearRect (0, 0, canvasWidth, canvasHeight); drawworld (verden, ctx); // 3 setTimeout ('step ()', 10); 

Det vi oppnår her:

  1. Instruert box2dWorld å utføre fysikk simuleringer
  2. Rydde lerretskjermen og tegne igjen
  3. Utfør skritt() fungere igjen om ti millisekunder

Med denne koden, jobber vi nå med fysikk og tegning. Du kan teste deg selv og se etter en fallende ball, som vist nedenfor:


drawWorld i box2dutils.js

funksjon drawWorld (verden, kontekst) for (var j = world.m_jointList; j; j = j.m_next) drawJoint (j, kontekst);  for (var b = world.m_bodyList; b; b = b.m_next) for (var s = b.GetShapeList (); s! = null; s = s.GetNext ()) drawShape (s, kontekst) ; 

Hva vi har skrevet ovenfor, er en feilsøkingsfunksjon som trekker verden inn i lerretet, ved hjelp av grafikk-APIen som tilbys av HTML5s lærred API.

Den første sløyfen trekker alle leddene. Vi brukte ikke ledd i denne artikkelen. De er litt komplekse for en første demo, men likevel er de avgjørende for spillene dine. De lar deg lage veldig interessante kropper.

Den andre sløyfen trekker alle legemer, derfor er vi her!

funksjon drawShape (form, kontekst) context.strokeStyle = '# 000000'; context.beginPath (); bryter (shape.m_type) tilfelle b2Shape.e_circleShape: varcirkel = form; var pos = circle.m_position; var r = sirkel.m_radius; var segmenter = 16,0; var theta = 0,0; var dtheta = 2,0 * Math.PI / segmenter; // tegne sirkel context.moveTo (pos.x + r, pos.y); for (var i = 0; i < segments; i++)  var d = new b2Vec2(r * Math.cos(theta), r * Math.sin(theta)); var v = b2Math.AddVV(pos, d); context.lineTo(v.x, v.y); theta += dtheta;  context.lineTo(pos.x + r, pos.y); // draw radius context.moveTo(pos.x, pos.y); var ax = circle.m_R.col1; var pos2 = new b2Vec2(pos.x + r * ax.x, pos.y + r * ax.y); context.lineTo(pos2.x, pos2.y);  break; case b2Shape.e_polyShape:  var poly = shape; var tV = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[0])); context.moveTo(tV.x, tV.y); for (var i = 0; i < poly.m_vertexCount; i++)  var v = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[i])); context.lineTo(v.x, v.y);  context.lineTo(tV.x, tV.y);  break;  context.stroke(); 

Vi løper gjennom alle hjørner av objektet og tegner det med linjer (context.moveTo og context.lineTo). Nå er det nyttig å ha et eksempel? men ikke så nyttig i praksis. Når du bruker grafikk, trenger du bare å være oppmerksom på kroppens posisjonering. Du trenger ikke å løse hjørner, som denne demoen gjør.


Trinn 4 - Interaktivitet

Et spill uten interaktivitet er en film, og en film med interaktivitet er et spill.

La oss utvikle tastaturpilenes funksjonalitet for å hoppe og flytte ballen.

Legg til følgende kode i din game.js fil:

funksjonshåndtakKeyDown (evt) keys [evt.keyCode] = true;  funksjonshåndtakKeyUp (evt) keys [evt.keyCode] = false;  // deaktiver vertikal rulling fra pilene :) document.onkeydown = function () return event.keyCode! = 38 && event.keyCode! = 40

Med handleKeyDown og handleKeyUp, vi setter opp en matrise som sporer hver nøkkel brukeren skriver. Med document.onkeydown, vi deaktiverer nettleserens opprinnelige vertikale rullefunksjon for opp- og nedpiler. Har du noen gang spilt et HTML5-spill, og når du hopper, går spilleren, fiender og gjenstander av skjermen? Det blir ikke et problem nå.

Legg til denne neste koden i begynnelsen av din skritt() funksjon:

handleInteractions ();

Og utenfor, erklærer funksjonen:

funksjonshåndtakInteraksjoner () // opp pil // 1 var kollisjon = world.m_contactList; player.canJump = false; if (collision.GetShape1 (). GetUserData () == 'spiller' || collision.GetShape2 (). GetUserData () == 'spiller') if ((collision.GetShape1 () .GetUserData () == 'bakken' || collision.GetShape2 (). GetUserData () == 'bakken')) var playerObj = (collision.GetShape1 (). GetUserData () == 'spiller'? Kollisjon.GetShape1 () .GetPosition (): collision.GetShape2 (). GetPosition ()); var groundObj = (collision.GetShape1 (). GetUserData () == 'bakken'? Collision.GetShape1 (). GetPosition (): collision.GetShape2 (). GetPosition ()); hvis (playerObj.y < groundObj.y) player.canJump = true;     // 2 var vel = player.object.GetLinearVelocity(); // 3 if (keys[38] && player.canJump) vel.y = -150;  // 4 // left/right arrows if (keys[37]) vel.x = -60;  else if (keys[39]) vel.x = 60;  // 5 player.object.SetLinearVelocity(vel); 

Den mest kompliserte delen av koden ovenfor er den første, der vi ser etter en kollisjon, og skriv noen forhold for å avgjøre om shape1 eller shape2 er spilleren. Hvis det er, bekrefter vi om shape1 eller shape2 er en bakke gjenstand. Igjen, hvis ja, kolliderer spilleren med bakken. Deretter sjekker vi om spilleren er over bakken. Hvis det er tilfelle, kan spilleren hoppe.

På den andre kommenterte linjen (2) henter vi LinearVelocity av spilleren.

Tredje og fjerde kommenterte regioner verifiserer om piler trykkes, og juster hastighetsvektoren, tilsvarende.

I den femte regionen installerer vi spilleren med den nye hastighetsvektoren.

Samspillet er nå gjort! Men det er ikke noe mål, vi hopper bare, hopper, hopper? og hopp!


Trinn 5 - "Du Vinner" Melding

Legg til koden nedenfor til begynnelsen av din LinearVelocity funksjon:

 hvis (player.object.GetCenterPosition (). y> canvasHeight) player.object.SetCenterPosition (ny b2Vec2 (20,0), 0) annet hvis (player.object.GetCenterPosition (). x> canvasWidth-50)  showWin (); komme tilbake; 
  • Den første tilstanden bestemmer om spilleren faller, og skal transporteres tilbake til startpunktet (over vestplattformen).
  • Den andre betingelsen kontrollerer om spilleren er over den andre plattformen, og vant spillet. Her er showWin () funksjon.
funksjon showWin () ctx.fillStyle = '# 000'; ctx.font = '30px verdana'; ctx.textBaseline = 'top'; ctx.fillText ('Du! Du gjorde det!', 30, 0); ctx.fillText ('takk, andersonferminiano.com', 30, 30); ctx.fillText ('@ andferminiano', 30, 60); 

Og det er det! Du har nettopp fullført ditt første enkle spill med HTML5 og Box2D. Gratulerer!

Hvis du trenger en enklere løsning, kan du sjekke ut valget av HTML5-spill på Envato Market, hvorav mange kommer med kildekoden for å undersøke og tilpasse for å passe dine behov.