Membuat Game Berbasis Kanvas HTML5: Tutorial Menggunakan AngularJS dan CreateJS
Diterbitkan: 2022-03-11Pengembangan game adalah salah satu teknik pemrograman canggih yang lebih menarik yang terus-menerus menantang industri pengembangan perangkat lunak.
Ada banyak platform pemrograman yang digunakan untuk mengembangkan game, dan ada sejumlah besar perangkat untuk memainkannya, tetapi ketika berbicara tentang bermain game di browser web, pengembangan berbasis Flash masih memimpin.
Dengan menulis ulang game berbasis Flash ke teknologi HTML5 Canvas, kami juga dapat memainkannya di browser seluler. Dan, dengan Apache Cordova, pengembang web yang terampil dapat dengan mudah menggabungkannya ke dalam aplikasi game seluler lintas platform.
Orang-orang di CreateJS mulai melakukan itu dan banyak lagi.
EaselJS , bagian dari suite CreateJS, membuat menggambar di Kanvas HTML5 menjadi sederhana. Bayangkan membangun visualisasi data khusus dengan kinerja tinggi dan ribuan elemen. Scalable Vector Graphic (SVG) bukanlah pilihan yang tepat, karena menggunakan elemen DOM. Browser menjadi kewalahan ketika, pada sekitar 600 elemen DOM, rendering awal, menggambar ulang, dan animasi menjadi operasi yang mahal. Dengan HTML5 Canvas, kita dapat dengan mudah mengatasi masalah ini; Gambar kanvas seperti tinta di atas kertas, tidak ada elemen DOM dan biaya terkaitnya.
Ini berarti bahwa pengembangan berbasis kanvas membutuhkan lebih banyak perhatian dalam hal memisahkan elemen, dan melampirkan peristiwa dan perilaku ke dalamnya. EaselJS datang untuk menyelamatkan; kita dapat membuat kode seolah-olah kita berurusan dengan elemen individual, membiarkan perpustakaan EaselJS menangani gerakan mouse, klik, dan tabrakan Anda.
Pengkodean berbasis SVG memiliki satu keuntungan besar: SVG memiliki spesifikasi lama, dan ada banyak alat desain yang mengekspor aset SVG untuk digunakan dalam pengembangan, sehingga kerjasama antara desainer dan pengembang berjalan dengan baik. Pustaka populer, seperti D3.JS, dan pustaka yang lebih baru dan lebih kuat seperti SnapSVG, menghadirkan banyak hal.
Jika alur kerja desainer-ke-pengembang adalah satu-satunya alasan Anda menggunakan SVG, pertimbangkan ekstensi untuk Adobe Illustrator (AI) yang menghasilkan kode dari bentuk yang dibuat di AI. Dalam konteks kami, ekstensi tersebut menghasilkan kode EaselJS atau kode ProcessingJS, keduanya merupakan pustaka berbasis Kanvas HTML5
Intinya, jika Anda memulai proyek baru, tidak ada alasan lagi untuk menggunakan SVG!
SoundJS adalah bagian dari suite CreateJS; ini menyediakan API sederhana untuk spesifikasi Audio HTML5.
PreloadJS digunakan untuk preload aset seperti bitmap, file suara, dan sejenisnya. Ini bekerja dengan baik dalam kombinasi dengan perpustakaan CreateJS lainnya.
EaselJS, SoundJS, dan PreloadJS membuat pengembangan game menjadi sangat mudah untuk ninja JavaScript mana pun. Metode API-nya akrab bagi siapa saja yang menggunakan pengembangan game berbasis Flash.
“Ini semua hebat. Namun, bagaimana jika kita memiliki tim pengembang yang mengonversi banyak game dari Flash ke HTML5? Apakah mungkin melakukannya dengan suite ini?”
Jawabannya: “Ya, tetapi hanya jika semua pengembang Anda berada di level Jedi!”.
Jika Anda memiliki tim yang terdiri dari berbagai pengembang keahlian, yang sering terjadi, mungkin sedikit menakutkan untuk menggunakan CreateJS dan mengharapkan kode yang dapat diskalakan dan modular. Bagaimana jika kita menyatukan suite CreateJS dengan AngularJS? Bisakah kita mengurangi risiko ini dengan menghadirkan kerangka kerja JS front-end terbaik dan paling banyak diadopsi?
Ya , dan tutorial game Canvas HTML5 ini akan mengajarkan Anda cara membuat game dasar dengan CreateJS dan AngularJS!
Menanam Benih
AngularJS secara signifikan mengurangi kompleksitas dengan mengaktifkan tim pengembangan Anda dengan yang berikut:
- Menambahkan modularitas kode, sehingga anggota tim dapat fokus pada berbagai aspek permainan.
- Memecah kode menjadi bagian-bagian terpisah yang dapat diuji dan dipelihara.
- Mengaktifkan penggunaan kembali kode, sehingga satu kelas pabrik dapat dipakai beberapa kali, dan digunakan kembali untuk memuat aset dan perilaku yang berbeda tetapi serupa.
- Mempercepat pengembangan karena beberapa anggota tim dapat bekerja secara paralel, tanpa saling menginjak.
- Melindungi pengembang dari penggunaan pola yang buruk (Javascript membawa bagian yang sangat buruk dan JSLint hanya dapat banyak membantu kami).
- Menambahkan kerangka pengujian yang solid.
Jika, seperti saya, Anda adalah seorang "pengacau" atau pembelajar taktil, Anda harus mendapatkan kode dari GitHub dan mulai belajar. Saran saya adalah untuk melihat check-in saya dan memahami langkah-langkah yang saya ambil untuk mendapatkan manfaat dari menambahkan kebaikan AngularJS ke kode CreateJS.
Menjalankan Proyek Benih AngularJS Anda
Jika Anda belum melakukannya, Anda perlu menginstal nodeJS sebelum Anda dapat menjalankan demo ini.
Setelah membuat proyek benih AngularJS, atau mengunduhnya dari GitHub, jalankan npm install
untuk mengunduh semua dependensi ke folder aplikasi Anda.
Untuk menjalankan aplikasi Anda, jalankan npm start
dari folder yang sama dan navigasikan ke http://localhost:8000/app/#/view1
di browser Anda. Halaman Anda akan terlihat seperti gambar di bawah ini.
EaselJS Bertemu AngularJS
Tambahkan referensi perpustakaan CreateJS ke proyek benih AngularJS Anda. Pastikan skrip CreateJS disertakan setelah AngularJS.
<script src="http://code.createjs.com/createjs-2014.12.12.min.js"></script>
Selanjutnya, bersihkan aplikasi:
- Hapus folder view2 dari folder aplikasi Anda
- Hapus menu dan info versi AngularJS dari index.html, dengan menghapus kode yang ditunjukkan di bawah ini:
<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>
Hapus modul view2
dari app.js
, dengan menghapus baris berikut
myApp.view2,
Jika Anda belum pernah menggunakan AngularJS sebelumnya, dan tidak terbiasa dengan arahan AngularJS, lihat tutorial ini. Arahan di AngularJS adalah cara untuk mengajarkan HTML beberapa trik baru. Mereka adalah fitur yang paling dipikirkan dengan matang dalam kerangka kerja, dan membuat AngularJS kuat dan dapat diperluas.
Kapan pun Anda membutuhkan fungsionalitas atau komponen DOM khusus, carilah secara online; ada kemungkinan besar itu sudah tersedia di tempat-tempat seperti modul Angular.
Hal berikutnya yang perlu kita lakukan adalah membuat direktif AngularJS baru yang akan mengimplementasikan contoh dari EaselJS. Buat direktif baru bernama spriteSheetRunner di file baru yang terletak di /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); } } } });
Setelah arahan Anda dibuat, tambahkan ketergantungan ke aplikasi dengan memperbarui /app/app.js
seperti di bawah ini:
'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'}); }]);
Sertakan kode direktif dalam index.html
dengan menambahkan referensi ke spriteSheetRunner.js
.
<script src="view1/directives/spriteSheetRunner.js"></script>
Kami hampir siap! Salin aset game ke folder aplikasi Anda. Saya sudah menyiapkan gambarnya, jadi silakan unduh dan simpan di folder app/assets Anda.
- app/assets/spritesheet_grant.png
- app/assets/ground.png
- aplikasi/aset/bukit1.png
- aplikasi/aset/bukit2.png
- aplikasi/aset/sky.png
Sebagai langkah terakhir, tambahkan direktif kami yang baru dibuat ke halaman. Untuk melakukannya, ubah file app/view/view1.html
, dan buat menjadi satu baris:
<sprite-sheet-runner></sprite-sheet-runner>
Mulai aplikasi Anda dan Anda akan membuat pelari Anda bergerak :)
Jika ini adalah aplikasi AngularJS atau CreateJS pertama Anda, rayakan, Anda baru saja membuat sesuatu yang sangat keren!
Pramuat Aset dalam Layanan
Layanan di AngularJS adalah lajang yang digunakan terutama untuk berbagi kode dan data. Kami akan menggunakan layanan untuk membagikan 'aset game' di seluruh aplikasi. Untuk mempelajari lebih lanjut tentang layanan AngularJS, periksa dokumentasi AngularJS.
Layanan pengembangan AngularJS menyediakan mekanisme yang efektif untuk memuat dan mengelola semua aset di satu tempat. Perubahan aset disebarkan ke masing-masing instance layanan, membuat kode kami lebih mudah dipelihara.
Buat file JS baru bernama loaderSvc.js
di folder /app/view1/services
Anda.
//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 mengharuskan kami untuk mendaftarkan layanan apa pun yang kami gunakan. Untuk melakukannya, perbarui file app.js
Anda untuk menyertakan referensi ke 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', []);
Perbarui kode direktif Anda, dalam file app/view1/directives/spriteSheetRunner.js
, untuk menghapus kode pramuat dan gunakan layanan sebagai gantinya.
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); } } } }]);
Membuat Pabrik Elemen UI
Menggunakan kembali dan mengulang sprite dalam pengembangan game sangat penting. Untuk mengaktifkan instantiasi kelas UI (yang merupakan sprite dalam kasus kami), kami akan menggunakan Pabrik AngularJS.
Pabrik terdaftar dalam aplikasi seperti modul AngularJS lainnya. Untuk membuat pabrik uiClasses, ubah file app.js Anda agar terlihat seperti ini:
'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', []);
Mari kita gunakan pabrik baru untuk membuat langit, bukit, tanah, dan pelari kita. Untuk melakukannya, buat file JavaScript seperti yang tercantum di bawah ini.

- 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); }]);
Jangan lupa untuk menambahkan semua file JS baru ini di index.html
Anda.
Sekarang, kita perlu memperbarui arahan permainan.
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); } } } }]);
Perhatikan bahwa memindahkan uiClasses
dari direktif mengurangi ukuran direktif sebesar 20%, dari 91 menjadi 65 baris.
Selain itu, kami dapat menulis tes secara mandiri untuk setiap kelas pabrik untuk menyederhanakan perawatannya.
Catatan: Pengujian adalah topik yang tidak tercakup dalam posting ini tetapi di sini adalah tempat yang baik untuk memulai.
Interaksi Tombol Panah
Pada titik ini dalam tutorial permainan Kanvas HTML5 kami, klik atau ketuk mouse pada ponsel akan membuat pria kami melompat, dan kami tidak dapat menghentikannya. Mari tambahkan kontrol tombol panah:
- Panah kiri (jeda permainan)
- Panah atas (lompat)
- Panah kanan (mulai berlari)
Untuk melakukannya, buat fungsi keyDown
dan tambahkan event listener sebagai baris terakhir dari fungsi 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;
Coba jalankan game Anda lagi dan periksa kontrol keyboard.
Biarkan Musik Bermain
Game tidak menyenangkan tanpa musik, jadi ayo mainkan musik.
Pertama-tama kita perlu menambahkan file MP3 ke folder app/assets kita. Anda dapat mengunduhnya dari URL yang disediakan di bawah ini.
- aplikasi/aset/jump.mp3
- app/assets/runningTrack.mp3
Sekarang, kita perlu memuat file suara ini menggunakan layanan pemuat kami. Kami akan menggunakan loadQueue
dari perpustakaan PreloaderJS
. Perbarui app/view1/services/loaderSvc.js
untuk memuat file-file ini terlebih dahulu.
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/"); }; });
Ubah arahan game Anda untuk memutar suara di acara game.
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); } } } }]);
Menambahkan Skor dan Indikator Kehidupan
Mari tambahkan skor game dan indikator kehidupan (hati) ke game Kanvas HTML5. Skor akan ditampilkan sebagai angka di sudut kiri atas, dan simbol hati, di sudut kanan atas, akan menunjukkan hitungan kehidupan.
Kami akan menggunakan perpustakaan font eksternal untuk membuat hati, jadi tambahkan baris berikut ke header file index.html
Anda.
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">
Pengikatan AngularJS standar akan memberikan pembaruan waktu nyata. Tambahkan kode berikut ke file 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>
Untuk memposisikan indikator dengan benar, kita perlu menambahkan kelas CSS untuk kiri atas dan kanan atas di file app/app.css
.
.top-left { position: absolute; left: 30px; top: 10px; } .top-right { position: absolute; right: 100px; top: 10px; float: right; }
Inisialisasi skor dan variabel lifesCount
di pengontrol 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; }]);
Untuk memastikan bahwa indikator diperbarui dengan benar, ubah arahan permainan utama Anda untuk menggunakan variabel lingkup.
... replace : true, scope :{ score: '=score', lifesCount: '=lifesCount' }, template: ...
Untuk menguji pengikatan ruang lingkup, tambahkan tiga baris ini di akhir metode handleComplete()
.
scope.score = 10; scope.lifesCount = 2; scope.$apply();
Saat Anda menjalankan aplikasi, Anda akan melihat skor dan indikator kehidupan.
Ruang putih tambahan, di sebelah kanan halaman, akan terus ada karena kami masih melakukan hardcoding lebar dan tinggi game pada saat ini dalam tutorial pemrograman game HTML5 kami.
Menyesuaikan Lebar Game
AngularJS dikemas dengan metode dan layanan yang berguna. Salah satunya adalah $window, yang menyediakan properti innerWidth yang akan kita gunakan untuk menghitung posisi elemen kita.
Ubah app/view1/view1.js
untuk menyuntikkan layanan $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; }]);
Perluas arahan permainan utama dengan properti lebar dan tinggi dan hanya itu!
<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() { ...
Sekarang Anda memiliki permainan yang menyesuaikan diri dengan lebar jendela browser.
Jika Anda ingin mem-porting ini ke aplikasi seluler, saya sarankan membaca tutorial pengembangan aplikasi seluler saya yang lain tentang menggunakan kerangka kerja Ionic untuk membuat aplikasi seluler. Anda harus dapat membuat aplikasi benih ionik, menyalin semua kode dari proyek ini, dan mulai memainkan game di perangkat seluler Anda dalam waktu kurang dari satu jam.
Satu-satunya hal yang tidak saya bahas di sini adalah deteksi tabrakan. Untuk mempelajari lebih lanjut tentang itu, saya membaca artikel ini.
Bungkus
Saya percaya bahwa selama tutorial pengembangan game ini Anda menyadari bahwa AngularJS dan CreateJS adalah duo pemenang untuk pengembangan game berbasis HTML5. Anda memiliki semua dasar-dasarnya dan saya yakin Anda mengenali manfaat menggabungkan kedua platform ini.
Anda dapat mengunduh kode untuk artikel ini dari GitHub, silakan gunakan, bagikan, dan buat sendiri.