本文概述
游戏开发是更有趣的高级编程技术之一, 它不断挑战软件开发行业。
有许多用于开发游戏的编程平台, 并且有很多设备可以玩这些游戏, 但是, 在网络浏览器中玩游戏时, 基于Flash的开发仍然遥遥领先。
将基于Flash的游戏重写为HTML5 Canvas技术将使我们也可以在移动浏览器上进行游戏。而且, 借助Apache Cordova, 熟练的Web开发人员可以轻松地将它们包装到跨平台的移动游戏应用程序中。
CreateJS上的人们开始这样做以及更多。
EaselJS是CreateJS套件的一部分, 可简化在HTML5 Canvas上的绘制。想象一下, 构建具有高性能和数千个元素的自定义数据可视化。可伸缩矢量图形(SVG)是不正确的选择, 因为它使用DOM元素。当大约600个DOM元素使初始渲染, 重画和动画变得昂贵的操作时, 浏览器变得不知所措。使用HTML5 Canvas, 我们可以轻松解决这些问题。画布绘图就像纸上的墨水, 没有DOM元素及其相关成本。
这意味着基于Canvas的开发在分离元素以及将事件和行为附加到元素时需要更多的关注。 EaselJS进行了救援;我们可以像处理单个元素一样进行编码, 让EaselJS库处理你的鼠标悬停, 点击和碰撞。
基于SVG的编码具有一个很大的优势:SVG具有较旧的规范, 并且有许多设计工具可以导出SVG资产以供开发使用, 从而使设计人员与开发人员之间的合作良好。流行的库(例如D3.JS)和更新的, 功能更强大的库(例如SnapSVG)带来了很多好处。
如果从设计人员到开发人员的工作流程是你使用SVG的唯一原因, 请考虑使用Adobe Illustrator(AI)的扩展程序, 这些扩展程序可以从AI中创建的形状生成代码。在我们的上下文中, 此类扩展生成EaselJS代码或ProcessingJS代码, 两者都是基于HTML5 Canvas的库
最重要的是, 如果你要开始一个新项目, 则不再需要使用SVG!
SoundJS是CreateJS套件的一部分;它为HTML5音频规范提供了一个简单的API。
PreloadJS用于预加载资产, 例如位图, 声音文件等。与其他CreateJS库结合使用时效果很好。
EaselJS, SoundJS和PreloadJS使游戏开发对于任何JavaScript忍者来说都非常容易。使用基于Flash的游戏开发的任何人都熟悉其API方法。
“这一切都很棒。但是, 如果我们有一个开发人员团队将一堆游戏从Flash转换为HTML5怎么办?这个套件有可能做到吗?”
答案:”是的, 但前提是你的所有开发人员都处于绝地级别!”。
如果你有一个由不同技能集合的开发人员组成的团队(通常是这种情况), 那么使用CreateJS并期望具有可扩展的模块化代码可能会有些吓人。如果我们将CreateJS套件与AngularJS结合在一起怎么办?我们是否可以通过引入最佳和最常用的前端JS框架来减轻这种风险?
是的, 这个HTML5 Canvas游戏教程将教你如何使用CreateJS和AngularJS创建基本游戏!
播种
AngularJS通过使你的开发团队具备以下能力, 大大降低了复杂性:
- 添加代码模块化, 以便团队成员可以专注于游戏的不同方面。
- 将代码分为可测试和可维护的单独部分。
- 启用代码重用, 以便可以多次实例化一个工厂类, 然后将其重用于加载不同但相似的资产和行为。
- 由于多个团队成员可以并行工作而不会互相踩脚, 因此可以加快开发速度。
- 保护开发人员避免使用错误的模式(众所周知, JavaScript附带了错误的部分, 而JSLint只能提供很多帮助)。
- 添加可靠的测试框架。
如果像我一样, 你是”修补匠”或触觉学习者, 则应从GitHub获取代码并开始学习。我的建议是检查一下我的签入并了解我为从AngularJS优点添加到CreateJS代码中获益而采取的步骤。
运行你的AngularJS Seed项目
如果尚未执行此演示, 则需要先安装nodeJS。
创建AngularJS种子项目或从GitHub下载项目后, 运行npm install将所有依赖项下载到你的应用程序文件夹中。
要运行你的应用程序, 请从同一文件夹执行npm start并在浏览器中导航到http:// localhost:8000 / app /#/ view1。你的页面应如下图所示。
EaselJS与AngularJS相遇
将CreateJS库引用添加到AngularJS种子项目中。确保AngularJS之后包含CreateJS脚本。
<script src =” http://code.createjs.com/createjs-2014.12.12.min.js”> </ script>
接下来, 清理应用程序:
- 从你的应用程序文件夹中删除view2文件夹
- 通过删除以下代码, 从index.html删除菜单和AngularJS版本信息:
<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>
通过删除以下行, 从app.js中删除view2模块
myApp.view2,
如果你以前没有使用过AngularJS, 并且对AngularJS指令不熟悉, 请查看本教程。 AngularJS中的指令是教HTML一些新技巧的一种方式。它们是框架中经过深思熟虑的功能, 使AngularJS功能强大且可扩展。
每当你需要专用的DOM功能或组件时, 都可以在线搜索;它很有可能已经在Angular模块等地方可用。
我们需要做的下一步是创建一个新的AngularJS指令, 该指令将实现EaselJS中的示例。在/app/view1/directives/spriteSheetRunner.js中的新文件中创建一个名为spriteSheetRunner的新指令。
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);
}
}
}
});
创建指令后, 通过如下更新/app/app.js将依赖项添加到应用程序:
'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的引用, 将指令代码包括在index.html中。
<script src="view1/directives/spriteSheetRunner.js"></script>
我们快准备好了!将游戏资产复制到你的应用文件夹。我已经准备好图像, 请随时下载并保存在你的app / assets文件夹中。
- app / assets / spritesheet_grant.png
- app / assets / ground.png
- app / assets / hill1.png
- app / assets / hill2.png
- app / assets / sky.png
最后一步, 将我们新创建的指令添加到页面中。为此, 请更改你的app / view / view1.html文件, 并将其设置为单行:
<sprite-sheet-runner></sprite-sheet-runner>
启动你的应用程序, 你将使赛跑者动起来:)
如果这是你的第一个AngularJS或第一个CreateJS应用程序, 那么庆祝一下, 你所做的事情真的很酷!
在服务中预装资产
AngularJS中的服务是单例, 主要用于共享代码和数据。我们将使用一项服务在整个应用程序中共享”游戏资产”。要了解有关AngularJS服务的更多信息, 请查看AngularJS文档。
AngularJS开发服务提供了一种有效的机制, 可以在一处装载和管理所有资产。资产更改会传播到服务的每个单独实例, 从而使我们的代码更易于维护。
在/ app / view1 / services文件夹中创建一个名为loaderSvc.js的新JS文件。
//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要求我们注册正在使用的任何服务。为此, 更新你的app.js文件以包括对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', []);
在app / view1 / directives / spriteSheetRunner.js文件中更新指令代码, 以删除预加载代码并改用服务。
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元素工厂
在游戏开发中重用和重复精灵非常重要。为了启用UI类的实例化(在我们的例子中是sprite), 我们将使用AngularJS工厂。
就像任何其他AngularJS模块一样, Factory在应用程序中注册。要创建uiClasses工厂, 请将你的app.js文件修改为如下所示:
'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', []);
让我们使用新工厂来创建天空, 丘陵, 地面和我们的跑步者。为此, 创建如下所示的JavaScript文件。
- 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);
}]);
不要忘记将所有这些新的JS文件添加到index.html中。
现在, 我们需要更新游戏指令。
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移出指令将指令大小从91行减少了20%至65行。
此外, 我们可以为每个工厂类别独立编写测试以简化其维护。
注意:测试是本文中未涉及的主题, 但是这是一个不错的起点。
方向键交互
此时, 在我们的HTML5 Canvas游戏教程中, 在移动设备上单击鼠标或点击将使我们的人跳起来, 而我们无法阻止他。让我们添加箭头键控件:
- 向左箭头(暂停游戏)
- 向上箭头(跳)
- 向右箭头(开始运行)
为此, 创建keyDown函数并将事件侦听器添加为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;
尝试再次运行游戏, 然后检查键盘控件。
让音乐播放
没有音乐, 游戏不会很有趣, 所以让我们播放一些音乐。
我们首先需要将MP3文件添加到我们的app / assets文件夹中。你可以从下面提供的URL下载它们。
- 应用程序/资产/jump.mp3
- 应用程序/资产/runningTrack.mp3
现在, 我们需要使用加载程序服务来预加载这些声音文件。我们将使用PreloaderJS库的loadQueue。更新你的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"}, {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/");
};
});
修改你的游戏指令以在游戏事件上播放声音。
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);
}
}
}
}]);
相关:srcmini开发人员的AngularJS最佳实践和技巧
添加得分和生活指标
让我们在HTML5 Canvas游戏中添加游戏得分和生命(心脏)指标。分数将在左上角显示为数字, 在右上角显示心脏符号, 以指示生命计数。
我们将使用外部字体库来渲染心脏, 因此将以下行添加到index.html文件头中。
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">
标准的AngularJS绑定将提供实时更新。将以下代码添加到你的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>
为了正确定位指标, 我们需要在app / app.css文件中为左上和右上添加CSS类。
.top-left {
position: absolute;
left: 30px;
top: 10px;
}
.top-right {
position: absolute;
right: 100px;
top: 10px;
float: right;
}
在app / view1 / view1.js控制器中初始化score和lifesCount变量。
'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;
}]);
为确保指示器已正确更新, 请修改你的主游戏指令以使用范围变量。
...
replace : true, scope :{
score: '=score', lifesCount: '=lifesCount'
}, template:
...
要测试范围绑定, 请在handleComplete()方法的末尾添加这三行。
scope.score = 10;
scope.lifesCount = 2;
scope.$apply();
当你运行该应用程序时, 你应该看到得分和寿命指标。
由于我们目前仍在HTML5游戏编程教程中对游戏的宽度和高度进行硬编码, 因此页面右侧将继续出现其他空格。
调整游戏宽度
AngularJS包含有用的方法和服务。其中之一是$ window, 它提供了innerWidth属性, 我们将使用该属性来计算元素的位置。
修改你的app / view1 / view1.js以注入$ 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;
}]);
扩展主要游戏指令的width和height属性, 就是这样!
<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() {
...
现在, 你可以让游戏根据浏览器窗口的宽度进行调整。
如果要将其移植到移动应用程序中, 建议阅读有关使用Ionic框架创建移动应用程序的其他移动应用程序开发教程。你应该能够创建一个离子种子应用程序, 复制该项目中的所有代码, 并在不到一个小时的时间内开始在移动设备上玩游戏。
我唯一在这里没有涉及的是碰撞检测。要了解更多信息, 请阅读本文。
本文总结
我相信, 通过本游戏开发教程的学习, 你已经意识到AngularJS和CreateJS是基于HTML5的游戏开发的成功之选。你已经掌握了所有基础知识, 而且我相信你已经认识到结合使用这两个平台的好处。
你可以从GitHub下载本文的代码, 随时使用, 共享并自己制作。
相关:开发人员最常见的18个AngularJS错误
评论前必须登录!
注册