HTML5 Kanvas Tabanlı Oyun Yapma: AngularJS ve CreateJS Kullanan Bir Eğitim

Yayınlanan: 2022-03-11

Oyun geliştirme, yazılım geliştirme endüstrisine sürekli meydan okuyan daha ilginç, gelişmiş programlama tekniklerinden biridir.

Oyun geliştirmek için kullanılan birçok programlama platformu vardır ve bunları oynamak için çok sayıda cihaz vardır, ancak bir web tarayıcısında oyun oynamaya gelince, Flash tabanlı geliştirme hala başı çekiyor.

Flash tabanlı oyunları HTML5 Canvas teknolojisine yeniden yazmak, onları mobil tarayıcılarda da oynamamıza izin verir. Ayrıca, yetenekli web geliştiricileri Apache Cordova ile bunları platformlar arası mobil oyun uygulamalarına kolayca aktarabilir.

CreateJS çalışanları bunu ve daha fazlasını yapmak için yola çıktı.

CreateJS paketinin bir parçası olan EaselJS, HTML5 Canvas üzerinde çizim yapmayı basitleştirir. Yüksek performans ve binlerce öğeyle özel veri görselleştirmesi oluşturduğunuzu hayal edin. Ölçeklenebilir Vektör Grafiği (SVG), DOM öğelerini kullandığı için doğru seçim değildir. Yaklaşık 600 DOM öğesinde, ilk işlemeler, yeniden çizimler ve animasyonlar pahalı işlemler haline geldiğinde tarayıcılar bunalır. HTML5 Canvas ile bu sorunların üstesinden kolaylıkla gelebiliriz; Kanvas çizimler kağıt üzerindeki mürekkep gibidir, DOM öğeleri ve bunlarla ilişkili maliyetler yoktur.

Bu, Kanvas tabanlı geliştirmenin, öğelerin ayrılması ve bunlara olay ve davranışların eklenmesi söz konusu olduğunda daha fazla dikkat gerektirdiği anlamına gelir. EaselJS kurtarmaya gelir; sanki tek tek öğelerle uğraşıyormuşuz gibi kodlayabiliriz, EaselJS kitaplığının fareyle üzerine gelmelerinizi, tıklamalarınızı ve çarpışmalarınızı yönetmesine izin veririz.

SVG tabanlı kodlamanın büyük bir avantajı vardır: SVG'nin eski bir özelliği vardır ve geliştirmede kullanılmak üzere SVG varlıklarını dışa aktaran birçok tasarım aracı vardır, böylece tasarımcılar ve geliştiriciler arasındaki işbirliği iyi çalışır. D3.JS gibi popüler kitaplıklar ve SnapSVG gibi daha yeni, daha güçlü kitaplıklar, masaya çok şey getiriyor.

SVG'leri kullanmanızın tek nedeni tasarımcıdan geliştiriciye iş akışıysa, AI'da oluşturulan şekillerden kod üreten Adobe Illustrator (AI) uzantılarını düşünün. Bizim bağlamımızda, bu tür uzantılar, her ikisi de HTML5 Canvas tabanlı kitaplıklar olan EaselJS kodu veya ProcessingJS kodu oluşturur.

Sonuç olarak, yeni bir projeye başlıyorsanız, artık SVG'leri kullanmak için hiçbir neden yok!

SoundJS , CreateJS paketinin bir parçasıdır; HTML5 Ses özelliği için basit bir API sağlar.

PreloadJS , bitmapler, ses dosyaları ve benzerleri gibi varlıkları önceden yüklemek için kullanılır. Diğer CreateJS kitaplıkları ile birlikte iyi çalışır.

EaselJS, SoundJS ve PreloadJS, herhangi bir JavaScript ninjası için oyun geliştirmeyi çok kolaylaştırır. API yöntemleri, Flash tabanlı oyun geliştirmeyi kullanan herkese aşinadır.

"Bunların hepsi harika. Peki ya bir sürü oyunu Flash'tan HTML5'e dönüştüren bir geliştirici ekibimiz varsa? Bunu bu süitle yapmak mümkün mü?”

Cevap: “Evet, ancak tüm geliştiricileriniz Jedi seviyesindeyse!”.

Farklı beceri seti geliştiricilerinden oluşan bir ekibiniz varsa, ki bu genellikle böyledir, CreateJS kullanmak ve ölçeklenebilir ve modüler bir kod beklemek biraz korkutucu olabilir. AngularJS ile CreateJS paketini bir araya getirirsek ne olur? En iyi ve en çok benimsenen ön uç JS çerçevesini getirerek bu riski azaltabilir miyiz?

Evet ve bu HTML5 Canvas oyun öğreticisi size CreateJS ve AngularJS ile nasıl temel bir oyun oluşturacağınızı öğretecek!

CreateJS ve AngularJS ile HTML5 Canvas oyun eğitimi

Tohumu Dikmek

AngularJS, geliştirme ekibinize aşağıdakileri sağlayarak karmaşıklığı önemli ölçüde azaltır:

  1. Ekip üyelerinin oyunun farklı yönlerine odaklanabilmeleri için kod modülerliği ekleme.
  2. Kodu ayrı test edilebilir ve bakımı yapılabilir parçalara bölmek.
  3. Bir fabrika sınıfının birden çok kez başlatılabilmesi ve farklı ancak benzer varlıkları ve davranışları yüklemek için yeniden kullanılabilmesi için kodun yeniden kullanımını etkinleştirme.
  4. Geliştirmeyi hızlandırır çünkü birden fazla ekip üyesi birbirinin ayağına basmadan paralel olarak çalışabilir.
  5. Geliştiricileri kötü kalıplar kullanmaktan korumak (Javascript, kötü şöhrete sahip kısımlarını beraberinde taşır ve JSLint bize ancak bu kadar yardımcı olabilir).
  6. Sağlam bir test çerçevesi ekleme.

Eğer benim gibi “tamirci” veya dokunsal bir öğreniciyseniz, kodu GitHub'dan almalı ve öğrenmeye başlamalısınız. Benim önerim, check-in'lerime göz atmak ve CreateJS koduna AngularJS iyiliği eklemenin faydalarını elde etmek için attığım adımları anlamak.

AngularJS Seed Projenizi Çalıştırma

Henüz yapmadıysanız, bu demoyu çalıştırmadan önce nodeJS'yi yüklemeniz gerekir.

Bir AngularJS tohum projesi oluşturduktan veya GitHub'dan indirdikten sonra, tüm bağımlılıkları uygulama klasörünüze indirmek için npm install çalıştırın.

Uygulamanızı çalıştırmak için aynı klasörden npm start ve tarayıcınızda http://localhost:8000/app/#/view1 konumuna gidin. Sayfanız aşağıdaki resimdeki gibi görünmelidir.

sayfa örneği

EaselJS, AngularJS ile Buluşuyor

AngularJS tohum projenize CreateJS kitaplığı referansı ekleyin. AngularJS'den sonra CreateJS betiğinin eklendiğinden emin olun.

<script src="http://code.createjs.com/createjs-2014.12.12.min.js"></script>

Ardından, uygulamayı temizleyin:

  • Uygulama klasörünüzden view2 klasörünü silin
  • Aşağıda gösterilen kodu silerek menüyü ve AngularJS sürüm bilgisini index.html'den kaldırın:
 <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>

Aşağıdaki satırı silerek view2 modülünü app.js kaldırın

myApp.view2,

Daha önce AngularJS kullanmadıysanız ve AngularJS yönergelerine aşina değilseniz, bu eğiticiye göz atın. AngularJS'deki yönergeler, HTML'ye bazı yeni numaralar öğretmenin bir yoludur. Çerçevedeki en iyi düşünülmüş özelliklerdir ve AngularJS'yi güçlü ve genişletilebilir kılar.

Özel bir DOM işlevine veya bir bileşene ihtiyaç duyduğunuzda, onu çevrimiçi arayın; Angular modüller gibi yerlerde zaten mevcut olma ihtimali yüksektir.

Bir sonraki yapmamız gereken şey, EaselJS örneğini uygulayacak yeni bir AngularJS yönergesi oluşturmaktır. /app/view1/directives/spriteSheetRunner.js içinde bulunan yeni bir dosyada spriteSheetRunner adlı yeni bir yönerge oluşturun.

 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); } } } });

Yönergeniz oluşturulduktan sonra, /app/app.js aşağıdaki gibi güncelleyerek uygulamaya bir bağımlılık ekleyin:

 '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'}); }]);

spriteSheetRunner.js bir başvuru ekleyerek yönerge kodunu index.html içine dahil edin.

 <script src="view1/directives/spriteSheetRunner.js"></script>

Neredeyse hazırız! Oyun varlıklarını uygulama klasörünüze kopyalayın. Resimleri hazırladım, bu yüzden onları indirmekten ve uygulama/varlıklar klasörünüze kaydetmekten çekinmeyin.

  • app/asset/spritesheet_grant.png
  • uygulama/varlıklar/zemin.png
  • uygulama/varlıklar/hill1.png
  • uygulama/varlıklar/hill2.png
  • uygulama/varlıklar/sky.png

Son adım olarak yeni oluşturduğumuz yönergemizi sayfaya ekleyin. Bunu yapmak için app/view/view1.html dosyanızı değiştirin ve onu tek satırlı yapın:

 <sprite-sheet-runner></sprite-sheet-runner>

Başvurunuzu başlatın ve koşucunuzu harekete geçireceksiniz :)

hareket halindeki koşucu

Bu sizin ilk AngularJS veya ilk CreateJS uygulamanızsa, kutlayın, gerçekten harika bir şey yaptınız!

Bir Hizmette Varlıkları Önceden Yükleme

AngularJS'deki hizmetler, öncelikle kodu ve verileri paylaşmak için kullanılan tekillerdir. Uygulama genelinde 'oyun varlıklarını' paylaşmak için bir hizmet kullanacağız. AngularJS hizmetleri hakkında daha fazla bilgi edinmek için AngularJS belgelerine bakın.

AngularJS geliştirme hizmetleri, tüm varlıkları tek bir yerde yüklemek ve yönetmek için etkili bir mekanizma sağlar. Varlık değişiklikleri, bir hizmetin her bir örneğine yayılır, bu da kodumuzun bakımını çok daha kolay hale getirir.

/app/view1/services klasörünüzde loaderSvc.js adlı yeni JS dosyası oluşturun.

 //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, kullandığımız herhangi bir hizmeti kaydetmemizi gerektirir. Bunu yapmak için app.js dosyanızı myApp.services referansını içerecek şekilde güncelleyin.

 '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', []);

Ön yükleme kodunu kaldırmak ve bunun yerine hizmeti kullanmak için yönerge kodunuzu app/view1/directives/spriteSheetRunner.js dosyasında güncelleyin.

 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); } } } }]);

UI Öğeleri Fabrikası Oluşturma

Oyun geliştirmede spriteları tekrar kullanmak ve tekrarlamak çok önemlidir. UI sınıflarının (bizim durumumuzda sprite olan) somutlaştırılmasını sağlamak için AngularJS Fabrikalarını kullanacağız.

Fabrika, diğer tüm AngularJS modülleri gibi uygulamada kayıtlıdır. uiClasses fabrikası oluşturmak için app.js dosyanızı şöyle görünecek şekilde değiştirin:

 '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', []);

Gökyüzü, tepe, yer ve koşucumuzu oluşturmak için yeni fabrikayı kullanalım. Bunu yapmak için, aşağıda listelendiği gibi JavaScript dosyaları oluşturun.

  • 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); }]);

Tüm bu yeni JS dosyalarını index.html dosyanıza eklemeyi unutmayın.

Şimdi oyun yönergesini güncellememiz gerekiyor.

 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); } } } }]);

uiClasses yönergeden çıkarmanın yönerge boyutunu 91 satırdan 65 satıra %20 oranında azalttığını unutmayın.

Ek olarak, bakımını basitleştirmek için her fabrika sınıfı için bağımsız olarak testler yazabiliriz.

Not: Test, bu gönderide ele alınmayan bir konudur, ancak burada başlamak için iyi bir yer.

Ok Tuşları Etkileşimi

HTML5 Canvas oyun öğreticimizin bu noktasında, bir cep telefonuna fare tıklaması veya dokunma, adamımızın zıplamasına neden olacak ve onu durduramayız. Ok tuşu kontrollerini ekleyelim:

  • Sol ok (oyunu duraklat)
  • Yukarı ok (atlama)
  • Sağ ok (koşmaya başlayın)

Bunu yapmak için keyDown işlevini oluşturun ve handleComplete() işlevinin son satırı olarak bir olay dinleyicisi ekleyin.

 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;

Oyununuzu tekrar çalıştırmayı deneyin ve klavye kontrollerini kontrol edin.

Bırak müzik çalsın

Oyunlar müziksiz eğlenceli değildir, o yüzden biraz müzik çalalım.

Önce uygulama/varlıklar klasörümüze MP3 dosyaları eklememiz gerekecek. Bunları aşağıda verilen URL'lerden indirebilirsiniz.

  • uygulama/varlıklar/jump.mp3
  • uygulama/varlıklar/runningTrack.mp3

Şimdi, yükleyici hizmetimizi kullanarak bu ses dosyalarını önceden yüklememiz gerekiyor. PreloaderJS kütüphanesinin loadQueue kullanacağız. Bu dosyaları önceden yüklemek için app/view1/services/loaderSvc.js güncelleyin.

 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/"); }; });

Oyun etkinliklerinde ses çalmak için oyun yönergenizi değiştirin.

 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); } } } }]);
İlgili: Toptal Geliştiricilerinden AngularJS En İyi Uygulamaları ve İpuçları

Puan ve Yaşam Göstergeleri Ekleme

HTML5 Canvas oyununa oyun skorunu ve can (kalp) göstergelerini ekleyelim. Skor sol üst köşede bir sayı olarak gösterilecek ve sağ üst köşedeki kalp sembolleri yaşam sayısını gösterecektir.

Kalpleri oluşturmak için harici bir yazı tipi kitaplığı kullanacağız, bu nedenle aşağıdaki satırı index.html dosya başlığınıza ekleyin.

 <link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">

Standart AngularJS bağlaması, gerçek zamanlı güncellemeler sağlayacaktır. app/view1/view1.html dosyanıza aşağıdaki kodu ekleyin:

 <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>

Göstergelerimizi doğru bir şekilde konumlandırmak için app/app.css dosyasına sol üst ve sağ üst için CSS sınıfları eklememiz gerekiyor.

 .top-left { position: absolute; left: 30px; top: 10px; } .top-right { position: absolute; right: 100px; top: 10px; float: right; }

app/view1/view1.js denetleyicisinde puanı ve lifesCount değişkenlerini başlatın.

 '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; }]);

Göstergelerin düzgün bir şekilde güncellendiğinden emin olmak için ana oyun yönergenizi kapsam değişkenlerini kullanacak şekilde değiştirin.

 ... replace : true, scope :{ score: '=score', lifesCount: '=lifesCount' }, template: ...

Kapsam bağlamayı test etmek için bu üç satırı handleComplete() yönteminin sonuna ekleyin.

 scope.score = 10; scope.lifesCount = 2; scope.$apply();

Uygulamayı çalıştırdığınızda puan ve yaşam göstergelerini görmelisiniz.

puan ve yaşam göstergeleri

Sayfanın sağındaki ek beyaz alan, HTML5 oyun programlama eğitimimizin bu noktasında oyunun genişliğini ve yüksekliğini hala kodladığımız için mevcut olmaya devam edecek.

Oyun Genişliğini Uyarlama

AngularJS, kullanışlı yöntemler ve hizmetlerle doludur. Bunlardan biri, öğelerimizin konumunu hesaplamak için kullanacağımız innerWidth özelliğini sağlayan $window'dur.

$window hizmetini enjekte etmek için app/view1/view1.js değiştirin.

 '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; }]);

Ana oyun yönergesini genişlik ve yükseklik özellikleriyle genişletin ve hepsi bu kadar!

 <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() { ...

Artık oyun, tarayıcı penceresinin genişliğine göre kendini ayarlıyor.

Bunu bir mobil uygulamaya aktarmak istiyorsanız, mobil uygulamalar oluşturmak için Ionic çerçevesini kullanma hakkındaki diğer mobil uygulama geliştirme eğitimimi okumanızı öneririm. Bir iyonik tohum uygulaması oluşturabilmeli, bu projedeki tüm kodu kopyalayabilmeli ve oyunu bir saatten daha kısa sürede mobil cihazınızda oynamaya başlayabilmelisiniz.

Burada ele almadığım tek şey çarpışma tespiti. Bu konuda daha fazla bilgi edinmek için bu makaleyi okudum.

Sarmak

Bu oyun geliştirme eğitimi boyunca, AngularJS ve CreateJS'nin HTML5 tabanlı oyun geliştirme için kazanan ikili olduğunu fark ettiğinize inanıyorum. Tüm temel bilgilere sahipsiniz ve eminim bu iki platformu birleştirmenin faydalarını fark etmişsinizdir.

Bu makalenin kodunu GitHub'dan indirebilir, kullanmaktan, paylaşmaktan ve kendinizinkinden çekinmeyin.

İlgili: Geliştiricilerin Yaptığı En Yaygın 18 AngularJS Hatası