Crearea unui joc HTML5 pe bază de pânză: un tutorial folosind AngularJS și CreateJS
Publicat: 2022-03-11Dezvoltarea jocurilor este una dintre tehnicile de programare mai interesante și avansate care provoacă constant industria de dezvoltare de software.
Există multe platforme de programare folosite pentru a dezvolta jocuri și există o multitudine de dispozitive pe care să le jucați, dar atunci când vine vorba de a juca jocuri într-un browser web, dezvoltarea bazată pe Flash încă conduce.
Rescrierea jocurilor bazate pe Flash la tehnologia HTML5 Canvas ne-ar permite să le jucăm și pe browserele mobile. Și, cu Apache Cordova, dezvoltatorii web calificați le-ar putea include cu ușurință în aplicații de jocuri mobile multiplatforme.
Oamenii de la CreateJS și-au propus să facă asta și multe altele.
EaselJS , parte a suitei CreateJS, simplifică desenul pe HTML5 Canvas. Imaginați-vă să construiți o vizualizare personalizată a datelor cu performanță ridicată și mii de elemente. Graficul vectorial scalabil (SVG) nu este alegerea potrivită, deoarece folosește elemente DOM. Browserele devin copleșite atunci când, la aproximativ 600 de elemente DOM, redările inițiale, redesenările și animațiile devin operațiuni costisitoare. Cu HTML5 Canvas, putem rezolva cu ușurință aceste probleme; Desenele de pânză sunt ca cerneala pe hârtie, fără elemente DOM și costurile asociate acestora.
Aceasta înseamnă că dezvoltarea bazată pe Canvas are nevoie de mai multă atenție atunci când vine vorba de separarea elementelor și de atașarea evenimentelor și comportamentelor acestora. EaselJS vine în ajutor; putem codifica ca și cum am avea de-a face cu elemente individuale, lăsând biblioteca EaselJS să se ocupe de trecerea mouse-ului, clicurile și coliziunile.
Codarea bazată pe SVG are un mare avantaj: SVG are o specificație veche și există o mulțime de instrumente de proiectare care exportă active SVG pentru a fi utilizate în dezvoltare, astfel încât cooperarea dintre designeri și dezvoltatori să funcționeze bine. Bibliotecile populare, cum ar fi D3.JS, și bibliotecile mai noi și mai puternice, cum ar fi SnapSVG, aduc multe la masă.
Dacă fluxul de lucru de la designer la dezvoltator este singurul motiv pentru care ați folosi SVG-uri, luați în considerare extensiile pentru Adobe Illustrator (AI) care generează cod din formele create în AI. În contextul nostru, astfel de extensii generează cod EaselJS sau cod ProcessingJS, ambele fiind biblioteci bazate pe HTML5 Canvas
În concluzie, dacă începeți un nou proiect, nu mai există niciun motiv să utilizați SVG-uri!
SoundJS face parte din suita CreateJS; oferă un API simplu pentru specificația audio HTML5.
PreloadJS este folosit pentru a preîncărca elemente precum hărți de biți, fișiere de sunet și altele asemenea. Funcționează bine în combinație cu alte biblioteci CreateJS.
EaselJS, SoundJS și PreloadJS fac dezvoltarea jocului foarte ușoară pentru orice ninja JavaScript. Metodele sale API sunt familiare oricui a folosit dezvoltarea jocurilor bazate pe Flash.
„Totul acesta este grozav. Dar, ce se întâmplă dacă avem o echipă de dezvoltatori care convertesc o grămadă de jocuri din Flash în HTML5? Este posibil să faci asta cu această suită?”
Răspunsul: „Da, dar numai dacă toți dezvoltatorii tăi sunt la nivel Jedi!”.
Dacă aveți o echipă de dezvoltatori de seturi de abilități diferite, ceea ce este adesea cazul, poate fi puțin înfricoșător să utilizați CreateJS și să vă așteptați la un cod scalabil și modular. Ce se întâmplă dacă adunăm suita CreateJS cu AngularJS? Putem atenua acest risc prin introducerea celui mai bun și mai adoptat cadru JS front-end?
Da , iar acest tutorial de joc HTML5 Canvas vă va învăța cum să creați un joc de bază cu CreateJS și AngularJS!
Plantarea Sămânței
AngularJS reduce semnificativ complexitatea, permițând echipei dvs. de dezvoltare următoarele:
- Adăugarea de modularitate a codului, astfel încât membrii echipei să se poată concentra pe diferite aspecte ale jocului.
- Împărțirea codului în bucăți separate testabile și menținute.
- Permiterea reutilizarii codului, astfel încât o clasă din fabrică să poată fi instanțiată de mai multe ori și reutilizată pentru a încărca active și comportamente diferite, dar similare.
- Accelerarea dezvoltării deoarece mai mulți membri ai echipei pot lucra în paralel, fără a se călca unul pe celălalt.
- Protejarea dezvoltatorilor de utilizarea modelelor proaste (Javascript are părți notoriu dăunătoare cu el și JSLint ne poate ajuta doar atât de mult).
- Adăugarea unui cadru de testare solid.
Dacă, la fel ca mine, ești un „făcător” sau un învățător tactil, ar trebui să obții codul de pe GitHub și să începi să înveți. Sugestia mea este să mă uit la check-in-urile mele și să înțeleg pașii pe care i-am urmat pentru a obține beneficiile adăugării de bunătate AngularJS la codul CreateJS.
Rularea proiectului dvs. de semințe AngularJS
Dacă nu ați făcut deja acest lucru, trebuie să instalați nodeJS înainte de a putea rula această demonstrație.
După ce ați creat un proiect seed AngularJS sau l-ați descărcat din GitHub, rulați npm install
pentru a descărca toate dependențele în folderul aplicației dvs.
Pentru a rula aplicația, executați npm start
din același folder și navigați la http://localhost:8000/app/#/view1
în browser. Pagina ta ar trebui să arate ca imaginea de mai jos.
EaselJS întâlnește AngularJS
Adăugați referință la bibliotecă CreateJS la proiectul dvs. de semințe AngularJS. Asigurați-vă că scriptul CreateJS este inclus după AngularJS.
<script src="http://code.createjs.com/createjs-2014.12.12.min.js"></script>
Apoi, curățați aplicația:
- Ștergeți folderul view2 din folderul aplicației dvs
- Eliminați meniul și informațiile despre versiunea AngularJS din index.html, ștergând codul prezentat mai jos:
<ul class="menu"> <li><a href="#/view1">view1</a></li> <li><a href="#/view2">view2</a></li> </ul> … <div>Angular seed app: v<span app-version></span></div> … <script src="view2/view2.js"></script>
Eliminați modulul view2
din app.js
, ștergând următoarea linie
myApp.view2,
Dacă nu ați folosit AngularJS înainte și nu sunteți familiarizat cu directivele AngularJS, consultați acest tutorial. Directivele din AngularJS sunt o modalitate de a învăța HTML câteva trucuri noi. Sunt cea mai bine gândită caracteristică din cadru și fac AngularJS puternic și extensibil.
Ori de câte ori aveți nevoie de o funcționalitate DOM specializată sau de o componentă, căutați-o online; există șanse mari să fie deja disponibil în locuri precum modulele Angular.
Următorul lucru pe care trebuie să-l facem este să creăm o nouă directivă AngularJS care va implementa exemplul de la EaselJS. Creați o nouă directivă numită spriteSheetRunner într-un fișier nou situat în /app/view1/directives/spriteSheetRunner.js
.
angular.module('myApp.directives', []) .directive('spriteSheetRunner', function () { "use strict"; return { restrict : 'EAC', replace : true, scope :{ }, template: "<canvas width='960' height='400'></canvas>", link: function (scope, element, attribute) { var w, h, loader, manifest, sky, grant, ground, hill, hill2; drawGame(); function drawGame() { //drawing the game canvas from scratch here //In future we can pass stages as param and load indexes from arrays of background elements etc if (scope.stage) { scope.stage.autoClear = true; scope.stage.removeAllChildren(); scope.stage.update(); } else { scope.stage = new createjs.Stage(element[0]); } w = scope.stage.canvas.width; h = scope.stage.canvas.height; manifest = [ {src: "spritesheet_grant.png", id: "grant"}, {src: "sky.png", id: "sky"}, {src: "ground.png", id: "ground"}, {src: "hill1.png", id: "hill"}, {src: "hill2.png", id: "hill2"} ]; loader = new createjs.LoadQueue(false); loader.addEventListener("complete", handleComplete); loader.loadManifest(manifest, true, "/app/assets/"); } function handleComplete() { sky = new createjs.Shape(); sky.graphics.beginBitmapFill(loader.getResult("sky")).drawRect(0, 0, w, h); var groundImg = loader.getResult("ground"); ground = new createjs.Shape(); ground.graphics.beginBitmapFill(groundImg).drawRect(0, 0, w groundImg.width, groundImg.height); ground.tileW = groundImg.width; ground.y = h - groundImg.height; hill = new createjs.Bitmap(loader.getResult("hill")); hill.setTransform(Math.random() * w, h - hill.image.height * 4 - groundImg.height, 4, 4); hill.alpha = 0.5; hill2 = new createjs.Bitmap(loader.getResult("hill2")); hill2.setTransform(Math.random() * w, h - hill2.image.height * 3 - groundImg.height, 3, 3); var spriteSheet = new createjs.SpriteSheet({ framerate: 30, "images": [loader.getResult("grant")], "frames": {"regX": 82, "height": 292, "count": 64, "regY": 0, "width": 165}, // define two animations, run (loops, 1.5x speed) and jump (returns to run): "animations": { "run": [0, 25, "run", 1.5], "jump": [26, 63, "run"] } }); grant = new createjs.Sprite(spriteSheet, "run"); grant.y = 35; scope.stage.addChild(sky, hill, hill2, ground, grant); scope.stage.addEventListener("stagemousedown", handleJumpStart); createjs.Ticker.timingMode = createjs.Ticker.RAF; createjs.Ticker.addEventListener("tick", tick); } function handleJumpStart() { grant.gotoAndPlay("jump"); } function tick(event) { var deltaS = event.delta / 1000; var position = grant.x 150 * deltaS; var grantW = grant.getBounds().width * grant.scaleX; grant.x = (position >= w grantW) ? -grantW : position; ground.x = (ground.x - deltaS * 150) % ground.tileW; hill.x = (hill.x - deltaS * 30); if (hill.x hill.image.width * hill.scaleX <= 0) { hill.x = w; } hill2.x = (hill2.x - deltaS * 45); if (hill2.x hill2.image.width * hill2.scaleX <= 0) { hill2.x = w; } scope.stage.update(event); } } } });
Odată ce directiva dvs. este creată, adăugați o dependență la aplicație prin actualizarea /app/app.js
după cum urmează:
'use strict'; // Declare app level module which depends on views, and components angular.module('myApp',[ 'ngRoute', 'myApp.view1', 'myApp.version', 'myApp.services', 'myApp.uiClasses', 'myApp.directives']) .config(['$routeProvider', function($routeProvider) { $routeProvider.otherwise({redirectTo: '/view1'}); }]);
Includeți codul directivei în index.html
adăugând o referință la spriteSheetRunner.js
.
<script src="view1/directives/spriteSheetRunner.js"></script>
Suntem aproape gata! Copiați elementele jocului în folderul aplicației dvs. Am pregătit imaginile, așa că nu ezitați să le descărcați și să le salvați în dosarul dvs. aplicație/materiale.
- app/assets/spritesheet_grant.png
- app/assets/ground.png
- app/assets/hill1.png
- app/assets/hill2.png
- app/assets/sky.png
Ca pas final, adăugați directiva noastră nou creată pe pagină. Pentru a face acest lucru, schimbați fișierul app/view/view1.html
și transformați-l într-o singură linie:
<sprite-sheet-runner></sprite-sheet-runner>
Începeți aplicația și vă veți pune alergătorul în mișcare :)
Dacă aceasta este prima ta AngularJS sau prima aplicație CreateJS, sărbătorește, tocmai ai făcut ceva foarte tare!
Preîncărcarea activelor într-un serviciu
Serviciile din AngularJS sunt singleton-uri utilizate în principal pentru a partaja codul și datele. Vom folosi un serviciu pentru a partaja „activele jocului” în cadrul aplicației. Pentru a afla mai multe despre serviciile AngularJS, consultați documentația AngularJS.
Serviciile de dezvoltare AngularJS oferă un mecanism eficient pentru încărcarea și gestionarea tuturor activelor într-un singur loc. Modificările activelor sunt propagate la fiecare instanță individuală a unui serviciu, făcând codul nostru mult mai ușor de întreținut.
Creați un fișier JS nou numit loaderSvc.js
în folderul dvs. /app/view1/services
.
//app/view1/services/loaderSvc.js myServices.service('loaderSvc', function () { var manifest = [ {src: "spritesheet_grant.png", id: "grant"}, {src: "sky.png", id: "sky"}, {src: "ground.png", id: "ground"}, {src: "hill1.png", id: "hill"}, {src: "hill2.png", id: "hill2"} ], loader = new createjs.LoadQueue(true); this.getResult = function (asset) { return loader.getResult(asset); }; this.getLoader = function () { return loader; }; this.loadAssets = function () { loader.loadManifest(manifest, true, "/app/assets/"); }; });
AngularJS ne cere să înregistrăm orice serviciu pe care îl folosim. Pentru a face acest lucru, actualizați fișierul app.js
pentru a include referință la myApp.services
.
'use strict'; // Declare app level module which depends on views, and components angular.module('myApp',[ 'ngRoute', 'myApp.view1', 'myApp.version', 'myApp.services', 'myApp.directives']) .config(['$routeProvider', function($routeProvider) { $routeProvider.otherwise({redirectTo: '/view1'}); }]); var myServices = angular.module('myApp.services', []);
Actualizați codul de directivă, în app/view1/directives/spriteSheetRunner.js
, pentru a elimina codul de preîncărcare și pentru a utiliza serviciul.
angular.module('myApp.directives', []) .directive('spriteSheetRunner', ['loaderSvc', function (loaderSvc) { "use strict"; return { restrict : 'EAC', replace : true, scope :{ }, template: "<canvas width='960' height='400'></canvas>", link: function (scope, element, attribute) { var w, h, manifest, sky, grant, ground, hill, hill2; drawGame(); function drawGame() { //drawing the game canvas from scratch here //In future we can pass stages as param and load indexes from arrays of background elements etc if (scope.stage) { scope.stage.autoClear = true; scope.stage.removeAllChildren(); scope.stage.update(); } else { scope.stage = new createjs.Stage(element[0]); } w = scope.stage.canvas.width; h = scope.stage.canvas.height; loaderSvc.getLoader().addEventListener("complete", handleComplete); loaderSvc.loadAssets(); } function handleComplete() { sky = new createjs.Shape(); sky.graphics.beginBitmapFill(loaderSvc.getResult("sky")).drawRect(0, 0, w, h); var groundImg = loaderSvc.getResult("ground"); ground = new createjs.Shape(); ground.graphics.beginBitmapFill(groundImg).drawRect(0, 0, w + groundImg.width, groundImg.height); ground.tileW = groundImg.width; ground.y = h - groundImg.height; hill = new createjs.Bitmap(loaderSvc.getResult("hill")); hill.setTransform(Math.random() * w, h - hill.image.height * 4 - groundImg.height, 4, 4); hill.alpha = 0.5; hill2 = new createjs.Bitmap(loaderSvc.getResult("hill2")); hill2.setTransform(Math.random() * w, h - hill2.image.height * 3 - groundImg.height, 3, 3); var spriteSheet = new createjs.SpriteSheet({ framerate: 30, "images": [loaderSvc.getResult("grant")], "frames": {"regX": 82, "height": 292, "count": 64, "regY": 0, "width": 165}, // define two animations, run (loops, 1.5x speed) and jump (returns to run): "animations": { "run": [0, 25, "run", 1.5], "jump": [26, 63, "run"] } }); grant = new createjs.Sprite(spriteSheet, "run"); grant.y = 35; scope.stage.addChild(sky, hill, hill2, ground, grant); scope.stage.addEventListener("stagemousedown", handleJumpStart); createjs.Ticker.timingMode = createjs.Ticker.RAF; createjs.Ticker.addEventListener("tick", tick); } function handleJumpStart() { grant.gotoAndPlay("jump"); } function tick(event) { var deltaS = event.delta / 1000; var position = grant.x + 150 * deltaS; var grantW = grant.getBounds().width * grant.scaleX; grant.x = (position >= w + grantW) ? -grantW : position; ground.x = (ground.x - deltaS * 150) % ground.tileW; hill.x = (hill.x - deltaS * 30); if (hill.x + hill.image.width * hill.scaleX <= 0) { hill.x = w; } hill2.x = (hill2.x - deltaS * 45); if (hill2.x + hill2.image.width * hill2.scaleX <= 0) { hill2.x = w; } scope.stage.update(event); } } } }]);
Crearea UI Elements Factory
Reutilizarea și repetarea sprite-urilor în dezvoltarea jocului este foarte importantă. Pentru a activa instanțiarea claselor UI (care sunt sprites în cazul nostru) vom folosi AngularJS Factories.
Factory este înregistrată în aplicație la fel ca orice alt modul AngularJS. Pentru a crea o fabrică uiClasses, modificați fișierul app.js astfel încât să arate astfel:
'use strict'; // Declare app level module which depends on views, and components angular.module('myApp',[ 'ngRoute', 'myApp.view1', 'myApp.version', 'myApp.services', 'myApp.uiClasses', 'myApp.directives']) .config(['$routeProvider', function($routeProvider) { $routeProvider.otherwise({redirectTo: '/view1'}); }]); var uiClasses = angular.module('myApp.uiClasses', []); var myServices = angular.module('myApp.services', []);
Să folosim noua fabrică pentru a crea cerul, dealul, pământul și alergătorul nostru. Pentru a face acest lucru, creați fișiere JavaScript așa cum este listat mai jos.

- app/view1/uiClasses/sky.js
uiClasses.factory("Sky", [ 'loaderSvc', function (loaderSvc) { function Sky(obj) { this.sky = new createjs.Shape(); this.sky.graphics.beginBitmapFill(loaderSvc.getResult("sky")).drawRect(0, 0, obj.width, obj.height); } Sky.prototype = { addToStage: function (stage) { stage.addChild(this.sky); }, removeFromStage: function (stage) { stage.removeChild(this.sky); } }; return (Sky); }]);
- app/view1/uiClasses/hill.js
uiClasses.factory("Hill", [ 'loaderSvc', function (loaderSvc) { function Hill(obj) { this.hill = new createjs.Bitmap(loaderSvc.getResult(obj.assetName)); this.hill.setTransform(Math.random() * obj.width, obj.height - this.hill.image.height * obj.scaleFactor - obj.groundHeight, obj.scaleFactor, obj.scaleFactor); } Hill.prototype = { addToStage: function (stage) { stage.addChild(this.hill); }, removeFromStage: function (stage) { stage.removeChild(this.hill); }, setAlpha: function (val) { this.hill.alpha = val; }, getImageWidth: function () { return this.hill.image.width; }, getScaleX: function () { return this.hill.scaleX; }, getX: function () { return this.hill.x; }, getY: function () { return this.hill.y; }, setX: function (val) { this.hill.x = val; }, move: function (x, y) { this.hill.x = this.hill.x + x; this.hill.y = this.hill.y + y; } }; return (Hill); }]);
- app/view1/ground.js
uiClasses.factory("Ground", [ 'loaderSvc', function (loaderSvc) { function Ground(obj) { var groundImg = loaderSvc.getResult("ground"); this.ground = new createjs.Shape(); this.ground.graphics.beginBitmapFill(groundImg).drawRect(0, 0, obj.width + groundImg.width, groundImg.height); this.ground.tileW = groundImg.width; this.ground.y = obj.height - groundImg.height; this.height = groundImg.height; } Ground.prototype = { addToStage: function (stage) { stage.addChild(this.ground); }, removeFromStage: function (stage) { stage.removeChild(this.ground); }, getHeight: function () { return this.height; }, getX: function () { return this.ground.x; }, setX: function (val) { this.ground.x = val; }, getTileWidth: function () { return this.ground.tileW; }, move: function (x, y) { this.ground.x = this.ground.x + x; this.ground.y = this.ground.y + y; } }; return (Ground); }]);
- app/view1/uiClasses/character.js
uiClasses.factory("Character", [ 'loaderSvc', function (loaderSvc) { function Character(obj) { var spriteSheet = new createjs.SpriteSheet({ framerate: 30, "images": [loaderSvc.getResult(obj.characterAssetName)], "frames": {"regX": 82, "height": 292, "count": 64, "regY": 0, "width": 165}, // define two animations, run (loops, 1.5x speed) and jump (returns to run): "animations": { "run": [0, 25, "run", 1.5], "jump": [26, 63, "run"] } }); this.grant = new createjs.Sprite(spriteSheet, "run"); this.grant.y = obj.y; } Character.prototype = { addToStage: function (stage) { stage.addChild(this.grant); }, removeFromStage: function (stage) { stage.removeChild(this.grant); }, getWidth: function () { return this.grant.getBounds().width * this.grant.scaleX; }, getX: function () { return this.grant.x; }, setX: function (val) { this.grant.x = val; }, playAnimation: function (animation) { this.grant.gotoAndPlay(animation); } }; return (Character); }]);
Nu uitați să adăugați toate aceste noi fișiere JS în index.html
.
Acum, trebuie să actualizăm directiva de joc.
myDirectives.directive('spriteSheetRunner', ['loaderSvc','Sky', 'Ground', 'Hill', 'Character', function (loaderSvc, Sky, Ground, Hill, Character) { "use strict"; return { restrict : 'EAC', replace : true, scope :{ }, template: "<canvas width='960' height='400'></canvas>", link: function (scope, element, attribute) { var w, h, sky, grant, ground, hill, hill2; drawGame(); function drawGame() { //drawing the game canvas from scratch here if (scope.stage) { scope.stage.autoClear = true; scope.stage.removeAllChildren(); scope.stage.update(); } else { scope.stage = new createjs.Stage(element[0]); } w = scope.stage.canvas.width; h = scope.stage.canvas.height; loaderSvc.getLoader().addEventListener("complete", handleComplete); loaderSvc.loadAssets(); } function handleComplete() { sky = new Sky({width:w, height:h}); sky.addToStage(scope.stage); ground = new Ground({width:w, height:h}); hill = new Hill({width:w, height:h, scaleFactor: 4, assetName: 'hill', groundHeight: ground.getHeight()}); hill.setAlpha(0.5); hill.addToStage(scope.stage); hill2 = new Hill({width:w, height:h, scaleFactor: 3, assetName: 'hill2', groundHeight: ground.getHeight()}); hill2.addToStage(scope.stage); ground.addToStage(scope.stage); grant = new Character({characterAssetName: 'grant', y: 34}) grant.addToStage(scope.stage); scope.stage.addEventListener("stagemousedown", handleJumpStart); createjs.Ticker.timingMode = createjs.Ticker.RAF; createjs.Ticker.addEventListener("tick", tick); } function handleJumpStart() { grant.playAnimation("jump"); } function tick(event) { var deltaS = event.delta / 1000; var position = grant.getX() + 150 * deltaS; grant.setX((position >= w + grant.getWidth()) ? -grant.getWidth() : position); ground.setX((ground.getX() - deltaS * 150) % ground.getTileWidth()); hill.move(deltaS * -30, 0); if (hill.getX() + hill.getImageWidth() * hill.getScaleX() <= 0) { hill.setX(w); } hill2.move(deltaS * -45, 0); if (hill2.getX() + hill2.getImageWidth() * hill2.getScaleX() <= 0) { hill2.setX(w); } scope.stage.update(event); } } } }]);
Rețineți că mutarea uiClasses
din directivă a redus dimensiunea directivei cu 20%, de la 91 la 65 de linii.
În plus, putem scrie independent teste pentru fiecare clasă de fabrică pentru a simplifica întreținerea acesteia.
Notă: testarea este un subiect care nu este tratat în această postare, dar aici este un loc bun pentru a începe.
Interacțiunea tastelor săgeți
În acest moment al tutorialului nostru pentru jocul HTML5 Canvas, clic pe mouse sau atingere pe un mobil îl va face pe tipul nostru să sară și nu îl putem opri. Să adăugăm comenzile tastelor săgeți:
- Săgeată stânga (întrerupe jocul)
- săgeată sus (sărire)
- Săgeată la dreapta (începeți să alergați)
Pentru a face asta, creați funcția keyDown
și adăugați un ascultător de evenimente ca ultima linie a handleComplete()
.
function keydown(event) { if (event.keyCode === 38) {//if keyCode is "Up" handleJumpStart(); } if (event.keyCode === 39) {//if keyCode is "Right" if (scope.status === "paused") { createjs.Ticker.addEventListener("tick", tick); scope.status = "running"; } } if (event.keyCode === 37) {//if keyCode is "Left" createjs.Ticker.removeEventListener("tick", tick); scope.status = "paused"; } } window.onkeydown = keydown;
Încercați să rulați jocul din nou și verificați comenzile de la tastatură.
Să cânte muzica
Jocurile nu sunt distractive fără muzică, așa că haideți să punem puțină muzică.
Mai întâi va trebui să adăugăm fișiere MP3 în dosarul nostru aplicație/active. Le puteți descărca de la adresele URL furnizate mai jos.
- app/assets/jump.mp3
- app/assets/runningTrack.mp3
Acum, trebuie să preîncărcăm aceste fișiere de sunet folosind serviciul nostru de încărcare. Vom folosi loadQueue
din biblioteca PreloaderJS
. Actualizați-vă app/view1/services/loaderSvc.js
pentru a preîncărca aceste fișiere.
myServices.service('loaderSvc', function () { var manifest = [ {src: "spritesheet_grant.png", id: "grant"}, {src: "sky.png", id: "sky"}, {src: "ground.png", id: "ground"}, {src: "hill1.png", id: "hill"}, {src: "hill2.png", id: "hill2"}, {src: "runningTrack.mp3", id: "runningSound"}, {src: "jump.mp3", id: "jumpingSound"} ], loader = new createjs.LoadQueue(true); // need this so it doesn't default to Web Audio createjs.Sound.registerPlugins([createjs.HTMLAudioPlugin]); loader.installPlugin(createjs.Sound); this.getResult = function (asset) { return loader.getResult(asset); }; this.getLoader = function () { return loader; }; this.loadAssets = function () { loader.loadManifest(manifest, true, "/app/assets/"); }; });
Modificați directiva de joc pentru a reda sunete la evenimentele de joc.
myDirectives.directive('spriteSheetRunner', [ 'loaderSvc', 'Sky', 'Ground', 'Hill', 'Character', function (loaderSvc, Sky, Ground, Hill, Character) { "use strict"; return { restrict : 'EAC', replace : true, scope :{ }, template: "<canvas width='960' height='400'></canvas>", link: function (scope, element, attribute) { var w, h, sky, grant, ground, hill, hill2, runningSoundInstance, status; drawGame(); function drawGame() { //drawing the game canvas from scratch here if (scope.stage) { scope.stage.autoClear = true; scope.stage.removeAllChildren(); scope.stage.update(); } else { scope.stage = new createjs.Stage(element[0]); } w = scope.stage.canvas.width; h = scope.stage.canvas.height; loaderSvc.getLoader().addEventListener("complete", handleComplete); loaderSvc.loadAssets(); } function handleComplete() { sky = new Sky({width:w, height:h}); sky.addToStage(scope.stage); ground = new Ground({width:w, height:h}); hill = new Hill({width:w, height:h, scaleFactor: 4, assetName: 'hill', groundHeight: ground.getHeight()}); hill.setAlpha(0.5); hill.addToStage(scope.stage); hill2 = new Hill({width:w, height:h, scaleFactor: 3, assetName: 'hill2', groundHeight: ground.getHeight()}); hill2.addToStage(scope.stage); ground.addToStage(scope.stage); grant = new Character({characterAssetName: 'grant', y: 34}); grant.addToStage(scope.stage); scope.stage.addEventListener("stagemousedown", handleJumpStart); createjs.Ticker.timingMode = createjs.Ticker.RAF; createjs.Ticker.addEventListener("tick", tick); // start playing the running sound looping indefinitely runningSoundInstance = createjs.Sound.play("runningSound", {loop: -1}); scope.status = "running"; window.onkeydown = keydown; } function keydown(event) { if (event.keyCode === 38) {//if keyCode is "Up" handleJumpStart(); } if (event.keyCode === 39) {//if keyCode is "Right" if (scope.status === "paused") { createjs.Ticker.addEventListener("tick", tick); runningSoundInstance = createjs.Sound.play("runningSound", {loop: -1}); scope.status = "running"; } } if (event.keyCode === 37) {//if keyCode is "Left" createjs.Ticker.removeEventListener("tick", tick); createjs.Sound.stop(); scope.status = "paused"; } } function handleJumpStart() { if (scope.status === "running") { createjs.Sound.play("jumpingSound"); grant.playAnimation("jump"); } } function tick(event) { var deltaS = event.delta / 1000; var position = grant.getX() + 150 * deltaS; grant.setX((position >= w + grant.getWidth()) ? -grant.getWidth() : position); ground.setX((ground.getX() - deltaS * 150) % ground.getTileWidth()); hill.move(deltaS * -30, 0); if (hill.getX() + hill.getImageWidth() * hill.getScaleX() <= 0) { hill.setX(w); } hill2.move(deltaS * -45, 0); if (hill2.getX() + hill2.getImageWidth() * hill2.getScaleX() <= 0) { hill2.setX(w); } scope.stage.update(event); } } } }]);
Adăugarea scorului și a indicatorilor de viață
Să adăugăm scorul jocului și indicatorii de viață (inima) jocului HTML5 Canvas. Scorul va fi afișat ca un număr în colțul din stânga sus, iar simbolurile inimii, în colțul din dreapta sus, vor indica numărul de vieți.
Vom folosi o bibliotecă externă de fonturi pentru a reda inimi, așa că adăugați următoarea linie la antetul fișierului index.html
.
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">
Legarea standard AngularJS va oferi actualizări în timp real. Adăugați următorul cod în fișierul dvs. app/view1/view1.html
:
<sprite-sheet-runner score="score" lifes-count="lifesCount"></sprite-sheet-runner> <span class="top-left"><h2>Score: {{score}}</h2></span> <span class="top-right"><h2>Life: <i ng-if="lifesCount > 0" class="fa fa-heart"></i> <i ng-if="lifesCount < 1" class="fa fa-heart-o"></i> <i ng-if="lifesCount > 1" class="fa fa-heart"></i> <i ng-if="lifesCount < 2" class="fa fa-heart-o"></i> <i ng-if="lifesCount > 2" class="fa fa-heart"></i> <i ng-if="lifesCount < 3" class="fa fa-heart-o"></i> </h2></span>
Pentru a poziționa corect indicatorii noștri, trebuie să adăugăm clase CSS pentru stânga sus și dreapta sus în fișierul app/app.css
.
.top-left { position: absolute; left: 30px; top: 10px; } .top-right { position: absolute; right: 100px; top: 10px; float: right; }
Inițializați variabilele scor și lifesCount
în controlerul app/view1/view1.js
.
'use strict'; angular.module('myApp.view1', ['ngRoute']) .config(['$routeProvider', function($routeProvider) { $routeProvider.when('/view1', { templateUrl: 'view1/view1.html', controller: 'View1Ctrl' }); }]) .controller('View1Ctrl', ['$scope', function($scope) { $scope.score = 0; $scope.lifesCount = 3; }]);
Pentru a vă asigura că indicatorii sunt actualizați corespunzător, modificați directiva principală a jocului pentru a utiliza variabilele de domeniu.
... replace : true, scope :{ score: '=score', lifesCount: '=lifesCount' }, template: ...
Pentru a testa legarea domeniului, adăugați aceste trei linii la sfârșitul handleComplete()
.
scope.score = 10; scope.lifesCount = 2; scope.$apply();
Când rulați aplicația, ar trebui să vedeți indicatorii de scor și de viață.
Un spațiu alb suplimentar, în partea dreaptă a paginii, va continua să fie prezent, deoarece încă codificăm lățimea și înălțimea jocului în acest moment în tutorialul nostru de programare a jocului HTML5.
Adaptarea lățimii jocului
AngularJS este plin de metode și servicii utile. Unul dintre ele este $window, care oferă o proprietate innerWidth pe care o vom folosi pentru a calcula poziția elementelor noastre.
Modificați app/view1/view1.js
pentru a injecta serviciul $window
.
'use strict'; angular.module('myApp.view1', ['ngRoute']) .config(['$routeProvider', function($routeProvider) { $routeProvider.when('/view1', { templateUrl: 'view1/view1.html', controller: 'View1Ctrl' }); }]) .controller('View1Ctrl', ['$scope', '$window', function($scope, $window) { $scope.windowWidth = $window.innerWidth; $scope.gameHeight = 400; $scope.score = 0; $scope.lifesCount = 3; }]);
Extindeți directiva principală a jocului cu proprietăți de lățime și înălțime și gata!
<sprite-sheet-runner width="windowWidth" height="gameHeight" score="score" lifes-count="lifesCount"> </sprite-sheet-runner>
... scope :{ width: '=width', height: '=height', score: '=score', lifesCount: '=lifesCount' }, ... drawGame(); element[0].width = scope.width; element[0].height = scope.height; w = scope.width; h = scope.height; function drawGame() { ...
Acum jocul se adaptează la lățimea ferestrei browserului.
Dacă doriți să portați acest lucru într-o aplicație mobilă, vă sugerez să citiți celălalt tutorial de dezvoltare a aplicațiilor mobile despre utilizarea cadrului Ionic pentru a crea aplicații mobile. Ar trebui să puteți crea o aplicație de semințe ionice, să copiați tot codul din acest proiect și să începeți să jucați jocul pe dispozitivul dvs. mobil în mai puțin de o oră.
Singurul lucru pe care nu îl acopăr aici este detectarea coliziunilor. Pentru a afla mai multe despre el, am citit acest articol.
Învelire
Cred că pe parcursul acestui tutorial de dezvoltare a jocului ați realizat că AngularJS și CreateJS sunt un duo câștigător pentru dezvoltarea jocurilor bazate pe HTML5. Ai toate elementele de bază și sunt sigur că ai recunoscut beneficiile combinării acestor două platforme.
Puteți descărca codul pentru acest articol de pe GitHub, nu ezitați să îl utilizați, să îl distribuiți și să îl faceți al dvs.