65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 CreateJS 构建 Atari

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2013年1月21日

CPOL

14分钟阅读

viewsIcon

27070

使用 CreateJS 构建 Atari。

30 天开发一个 Windows 8 应用

本文最近出现在 Atari Arcade 开发者中心,这是 gskinner.comAtariInternet Explorer 团队合作的成果。

 

Atari Arcade 是最具挑战性的游戏开发项目之一,因为它需要跨越许多现代浏览器和设备实现全面的多点触控、多人游戏支持——没有 CreateJS,我们是无法完成的。我们从头开始重建了这些游戏,并且与一年前相比,在这些增强功能的帮助下,我们将项目所需时间缩短了一半。没有模拟器——一切,从设计到代码,都完全是 HTML5、CSS3 和 JS 的原创。而且也很有趣。

这个拆解将为您提供一些构建游戏的优秀最佳实践——我们如何利用这套工具集来构建我们对网络上现代街机游戏的愿景。我们已经进行了数百项改进,这些改进都可以通过 GitHub 上的开源方式供您使用。它包括预加载的游戏模板、预设的触摸控件、效果、得分助手以及一些用于基本碰撞和物理的预构建库。

其中许多灵感来自于 Internet Explorer 10 中新的功能和性能,这些功能使用户能够以全新的方式在网络上进行交互——通过 Windows 8 平板电脑设备的触摸。像《Asteroids》和《Missile Command》这样的经典游戏是展示以触摸为先的网络可能是什么样子的绝佳平台。

减少猜测,减少体力劳动

在很短的时间内,我们被赋予了设计和开发一个小型游戏库的任务,以展示 HTML5 在 支持触摸 的 Internet Explorer 10 中所能提供的最佳功能。我们的目标是保持原始 Atari 游戏灵魂的原汁原味,同时更新视觉效果、动画和游戏交互,以最好地适应当今现代浏览器中最新的标准驱动的网络技术。

这提供了一个机会,让我们能够真正按照 CreateJS 的预期方式使用它:承担与耗时且成本高昂的 HTML5 开发相关的繁重工作,并为开发人员构建现代网络交互式内容提供一种简单、熟悉的方法。

奖励:我们可以与 Atari 合作。

游戏设计和开发需要灵活、敏捷,并且能够并行执行——同时尽量减少在解决跨浏览器限制和不一致性上花费的时间。在其轻量级引擎下,CreateJS 提供了针对浏览器的敏感性,以及优先且优雅的后备方案,消除了技术方法的猜测,并减少了测试和调试所需的时间。

如果您是第一次听说 CreateJS,不妨看看它的内部构造

  • EaselJS:一个库,它基于 HTML5 Canvas 管理精灵、动画、矢量和位图绘图以及用户交互(包括多点触控)。它使用一个显示列表概念,该概念镜像了 Flash 中提供的概念。
  • TweenJS:一个简单且可链式的缓动引擎,可在一段时间内过渡数值或非数值。它针对 EaselJS 进行了优化,也可以单独使用。
  • SoundJS:一个音频播放引擎,它允许插件模块根据用户的能力使用各种方法播放声音。
  • PreloadJS:消除了预加载资产的猜测,无论它们是图像、视频、声音、JS、数据还是其他任何东西。
  • Zoë:从 Flash 动画配置和导出精灵表,并针对 EaselJS 进行了优化。

 

Atari Arcade SDK 补充了 CreateJS,为开发者提供了一些额外的工具。

  1. 用于实例化和控制游戏的框架,包括处理游戏结束场景、暂停、高分输入和显示等!
  2. 用于游戏预加载的资源定义。
  3. 用户输入(键盘、鼠标、触摸和多人输入),包括可自定义皮肤的控件。
  4. 用于常用字符串、数学、碰撞实用程序以及一些效果库的代码库。

 

有了 CreateJS 和 Atari Arcade SDK,您基本的客户端游戏开发基础就得到了保障。如果您来自 Flash 背景,EaselJS 和 CreateJS 套件的其余部分提供了一种熟悉且易于上手的方式来开发完全适用于 Internet Explorer 10 和其他现代浏览器的内容。

如果您想用 HTML5 制作游戏,确实没有比这套工具更好的入门选择了。

EaselJS - 更简单的 Canvas

EaselJS 是 CreateJS 套件的核心,同样,它也成为每个游戏的核心。

游戏的所有视觉元素都渲染到一个 EaselJS 舞台上,该舞台每 16-60 毫秒(可配置的 20 到 60 帧/秒)重新绘制一次。这个中心心跳,或“tick”,由 EaselJS Ticker 自动处理,它将文本、位图、矢量图形和动画属性的任何更改渲染到 HTML5 Canvas(在 Internet Explorer 10 等现代浏览器中得到良好支持且支持硬件加速)。

游戏中的视觉元素可以是 EaselJS 提供的任何显示对象。每个对象都提供了本应需要费力重新创建的功能。

核心功能

1. EaselJS 提供简单的显示对象(Bitmap、Graphics 和 Text),它们提供了一种简单的方法来控制舞台上可见对象的位置、变换、透明度、滤镜、可见性和其他属性。可以使用 Container 将元素分组并一起变换。

// Create a container (group)
var container = new createjs.Container();

// Transform and rotate. Any children will be affected.
container.x = 100;
container.rotation = 5;

// Create a shape
var shape = new createjs.Shape(
	new createjs.Graphics().beginFill("#999").drawRect(0,0,50,50)
);

// Create an image
var bitmap = new createjs.Bitmap("path/to/image.png");
bitmap.x = 100;

// Create some text
var text = new createjs.Text("Hello World", "24px Arial", "#990000");
text.x = 200;

// Add it all to the container, which is then put on the stage.
container.addChild(shape, bitmap, text);
stage.addChild(container);

通过这种方法,可以轻松地创建、更新和删除游戏中的元素(例如精灵、控件和游戏 HUD),而无需处理 canvas 上下文。

演示代码

<script type="text/javascript">
	var particleStage; 			// Stage
	var particleImage; // Particle sprite
	var particles = [];
	var emitterPoint = new createjs.Point(290, 195); 	// Point in container to spawn particles.

	window.addEventListener("load", initParticles, false)
	function initParticles() {
		particleStage = new createjs.Stage(document.getElementById("particleCanvas"));
		initDemo("particles", particleStage, startParticles, stopParticles);
	}

	function startParticles() {
		particleImage = new Image();
		particleImage.src = "assets/atariLogo.png";
		createjs.Ticker.addListener(particleTick);
	}

	function stopParticles() {
		createjs.Ticker.removeListener(particleTick);
		particles.length = 0;
	}

	function particleTick() {
		if (particles.length < 30) {
			var p = new createjs.Bitmap(particleImage);
			p.regX = 25;p.regY = 28;
			particleStage.addChild(p);
			initParticle(p);
			particles.push(p);
		}

		for(var i = 0, l = particles.length; i < l; i++) {
			var p = particles[i];
			p.x += p.vx;
			p.y += p.vy;
			p.vy += 0.9;
			p.alpha *= 0.94;
			p.rotation = Math.atan2(p.vy, p.vx) * 180/Math.PI + 180;
			if (p.y > 220 || p.alpha < 0.01) {
				initParticle(p);
			}
		}
		particleStage.update();
	}

	function initParticle(p) {
		p.x = emitterPoint.x; p.y = emitterPoint.y;
		p.vx = Math.random() * 30 - 15;
		p.vy = Math.random() * -20;
		p.alpha = 1;
		p.scaleX = p.scaleY = 1;
	}

</script>

2. 一个简化而强大的 Graphics 层,它允许使用熟悉的 API 创建矢量图形实例,并使用对代码友好的语法进行链式调用,甚至可以在多个形状之间共享。

// A Graphics object that represents the Atari Logo.
// Each graphic command is chained to the previous one.
var logo = new createjs.Graphics()
	.beginFill("#ff0000")
	.moveTo(37,3).lineTo(41,3).lineTo(41,23)
		.quadraticCurveTo(41,62,8,68)
		.lineTo(8,59).quadraticCurveTo(37,53,37,3).cp()
	.dr(44,3,10,65)
	.moveTo(61,3).lineTo(57,3).lineTo(57,23)
		.quadraticCurveTo(57,62,90,68)
		.lineTo(90,59).quadraticCurveTo(61,53,61,3).cp()

演示代码

	<script type="text/javascript">
		window.addEventListener("load", graphicsInit, false)
		function graphicsInit() {
			var stage = new createjs.Stage(document.getElementById("graphicsCanvas"));

			// Create a Graphics on the Atari Fuji Logo
			var logo = new createjs.Graphics()
				.f("#f00")
				.mt(37,3).lt(41,3).lt(41,23).qt(41,62,8,68).lt(8,59).qt(37,53,37,3).cp()
				.dr(44,3,10,65)
				.mt(61,3).lt(57,3).lt(57,23).qt(57,62,90,68).lt(90,59).qt(61,53,61,3).cp()

			// Draw 5 shapes using the same graphics.
			for (var i= 0; i<5; i++) {
				var s = new createjs.Shape(logo);
				s.x = 10+(i*110);
				s.y = 10;
				s.scaleX = s.scaleY = 1.2;
				stage.addChild(s);
			}

			// Redraw the stage one time.
			stage.update();
		}
	</script>

 

3. 使用精灵表的基于帧的动画非常适合将动画资源组合到单个图像中,而只需加载一次。

{"frames":{
	"width":52,
	"height":60
	"regX":0,
	"regY":0,
	"count":56,
}, "animations":{
	"CentipedeBodyWalk":[14, 26],
	"CentipedeWalk":[42, 55],
	"CentipedeButtWalk":[0, 12],
	"CentipedeBodyAltWalk":[28, 40],
	"CentipedeBodyWalkDown":[27, 27],
	"CentipedeButtDown":[13, 13],
	"BodyDown":[41, 41]
}, "images":["530428/centipede-article.png"]}

专业提示

  • 精灵表可以包含单个精灵的动画,但也可以在一个定义中包含多个精灵的信息,跨越多个 PNG 文件。
  • gskinner.com 的一个新工具 Omega,它使用单独的 alpha 通道来提供具有图像 alpha 透明度的 JPEG 图像的文件保存,这可以节省大量成本!
  • 用于 CreateJS 的 Flash 工具包可以导出紧凑的矢量数据,这些数据可以使用 SpriteSheetBuilder 动态转换为精灵表,从而在文件大小上节省成本,并提高运行时性能。

4. 图形和容器级别的缓存提供了更好的性能、GPU 支持以及随着时间推移动态合成纹理的能力。此外,EaselJS 的 SpriteSheetBuilder 类可以生成“运行时”精灵表,它提供了矢量图形的文件大小节省,以及位图缓存和硬件加速的运行时优势。

 

// Each time a shot is fired, the game stamps it into the arena.
function explodeShot(shot) {
	var stamp;

	// Determine the right stamp. The stamp is an EaselJS bitmap
	// Reuse instances, rather than creating new ones each time.
	if (shot.hitSomething) {
		stamp = this.craterStamp;
	} else {
		stamp = this.explosionStamp;
	}

	// Position the shot
	stamp.x = shot.x();
	stamp.y = shot.y();

	// Set some random scale/rotation
	stamp.scaleX = stamp.scaleY = 0.5 + Math.random() * 0.5;
	stamp.rotation = Math.random() * 360;

	// Stamp the shot in the arena
	this.arena.stamp(stamp);
}

// The Arena.js contains the stamp method
function stamp(stamp) {
	// Clear out any old children. They aren't drawn so they
	// only get removed when we want to stamp something new.
	this.groundLayer.removeAllChildren();

	// Stamp the new sprite on top of existing cache
	this.groundLayer.addChild(stamp);

	// Using source-over, it just applies on top
	this.groundLayer.updateCache("source-over");
}

这还没完。EaselJS 是 CreateJS 套件中最成熟的部分,并且拥有大量不断发展的特性,其中许多特性是在该项目生命周期中更新和引入的。

示例:Super Breakout

这里有一个简单的演示,展示了所有不同的显示对象。Super Breakout 使用了

  • 用于记分板的文本
  • 用于各种球的图形
  • 用于球轨迹的单个位图
  • 用于球拍和砖块的精灵表
  • 用于火花效果的缓存图形

 

用户交互

将一个伟大的概念、伟大的艺术和伟大的动画转化为一款伟大的游戏的关键在于用户交互。幸运的是,HTML 和 JavaScript 长期以来都支持强大的鼠标交互模型。不幸的是,使用 HTML5 中的 Canvas 的性质在该模型中引入了一个相当大的漏洞。原因如下:

当您的游戏渲染到 HTML5 Canvas 时,所有鼠标交互都由一个对象捕获:Canvas。对于渲染到像素的单个对象,并且该对象与该单个、平坦的 Canvas 中的所有其他像素合并(展平)后,没有预先设想的支持。

EaselJS 会为您处理这些。用户交互被转化为像素级精确的命中检测,以一种高性能、直接的方式。这是 EaselJS 舞台的精髓:它弥合了绘制到单个展平的 Canvas 与提供有用的信息以响应用户交互之间的差距。

 

示例:多点触控支持

Internet Explorer 10 和移动 Safari 等新的浏览器创新提供了强大、易于使用的触摸 API。直到最近,EaselJS 还只支持基本的单点输入。本项目是添加多点触控支持的催化剂,现在它已包含在开箱即用的功能中,无需您添加任何额外代码。不同的浏览器以不同的方式支持多点触控(遵循不同的 API),但是如果您使用 EaselJS,您无需担心。我们已经将跨浏览器不一致性抽象成了一个单一、直接的实现。

演示代码

<script>
// DRAG AND DROP DEMO
	var dragStage;
	var dragUpdate = true;

	window.addEventListener("load", initDragDemo, false);

	function initDragDemo() {
		// create stage and point it to the canvas:
		var canvas = document.getElementById("dragCanvas");
		dragStage = new createjs.Stage(canvas);
		initDemo("dragAndDrop", dragStage, startDragDemo, stopDragDemo);
	}

	function startDragDemo() {
		// enable touch interactions if supported on the current device:
		createjs.Touch.enable(dragStage);

		// enabled mouse over / out events
		dragStage.enableMouseOver(10);
		dragStage.mouseMoveOutside = true; // keep tracking the mouse even when it leaves the canvas

		// load the source image:
		var image = new Image();
		image.src = "assets/arcadeIcon-black.png";
		image.onload = handleDragImageLoad;
	}

	function stopDragDemo() {
		createjs.Ticker.removeListener(dragTick);
		createjs.Touch.disable(dragStage);
	}

	function handleDragImageLoad(event) {
		var image = event.target;

		// create and populate the screen with random images:
		for(var i = 0; i < 30; i++){
			var bitmap = new createjs.Bitmap(image);
			bitmap.regX = 48;
			bitmap.regY = 65;
			dragStage.addChild(bitmap);
			bitmap.x = 580 * Math.random()|0;
			bitmap.y = 200 * Math.random()|0;
			bitmap.rotation = 360 * Math.random()|0;

			// wrapper function to provide scope for the event handlers:
			bitmap.onPress = handleDragPress;
		}

		createjs.Ticker.addListener(dragTick);
		dragStage.update();
	}


	function handleDragPress(event) {
		var target = event.target;
		// bump the target in front of it's siblings:
		dragStage.addChild(target);
		dragUpdate = true;

		// add a handler to the event object's onMouseMove callback
		// this will be active until the user releases the mouse button:
		event.onMouseMove = function(moveEvent) {
			target.x = moveEvent.stageX;
			target.y = moveEvent.stageY;
			// indicate that the stage should be updated on the next tick:
			dragUpdate = true;
		}
	}

	function dragTick() {
		// this set makes it so the stage only re-renders when an event handler indicates a change has happened.
		if (dragUpdate) {
			dragUpdate = false; // only update once
			dragStage.update();
		}
	}
	</script>
	<canvas id="dragCanvas" width="580" height="200"></canvas>

 

var button = new createjs.Bitmap(imagePath);

// Standard display object mouse events
button.onPress = handlePress;

// Simple drag and drop. The events are automatically removed for you.
function handlePress(event) {
	event.onMouseMove = handleDrag;
}

function handleDrag(event) {
	button.x = event.stageX;
	button.y = event.stageY;
}

示例:触摸控件

对于 Atari Arcade,我们还仔细研究了用户通常对触摸交互的需求,并试图提供开箱即用的解决方案。操纵杆组件(用于《Asteroids》、《Yar's Revenge》、《Centipede》和《Combat》)跟踪定义区域内的鼠标交互,然后将拖动操作转换为熟悉的视觉表示以及游戏可以使用的标准化数据。

// Initialization is a great time to set up touch controls
function initialize(stage, assets, gameInfo) {

	if (gameInfo.touchEnabled) {

		// Create a joystick. There are lots of awesome
		// configurations, but this is all you need to get
		// started. Check out the docs for options!
		var joystick = new GameLibs.Joystick(stage);
		joystick.setPosition(25, 25);
		stage.addChild(joystick);
	}

}

TweenJS - 摆动、脉冲或滑动

游戏中大多数核心交互和动画使用程序化运动来响应不断变化的鼠标或键盘输入。然而,游戏的其他方面(过渡、定时动画、滴答作响的记分板等)需要更可预测的动画类型,这是由缓动引擎填补的角色。

TweenJS 采用通常的“从此处移动到彼处”的缓动方法,并增加了强大的链式调用功能,使其非常适合对游戏动画和事件进行排序。复杂的动画队列只需几行代码即可轻松控制。更棒的是,我们可以在缓动链的任何点轻松注入函数调用。

<!-- TweenSample demo -->
<script>
		var tweenStage;
		var yar;
		window.addEventListener("load", initTweenSample, false);
		function initTweenSample() {
			tweenStage = new createjs.Stage(document.getElementById("tweenSampleCanvas"));
			initDemo("tween", tweenStage, startTweenDemo, stopTweenDemo);
		}

		function startTweenDemo() {
			// Create a spritesheet manually.
			var ss = new createjs.SpriteSheet(
				{
					frames:{
						regX:0, regY:0,
						count:2,
						width:103, height:81
					}, images:["assets/yar.png"]
				}
			);

			// Create a bitmap animation, and put it on stage.
			yar = new createjs.BitmapAnimation(ss);
			yar.x = 50;
			yar.gotoAndStop(0);
			tweenStage.addChild(yar);

			// Tween the character back and forth, turning the eyes on and off.
			createjs.Tween.get(yar, {loop:true})
					.wait(3000)
					.call(yar.gotoAndStop, [1], yar) // red eyes
					.wait(300)
					.to({x:430}, 1200, createjs.Ease.backInOut) // move
					.call(yar.gotoAndStop, [0], yar)
					.wait(3000)
					.call(yar.gotoAndStop, [1], yar)
					.wait(300)
					.to({x:50}, 1200, createjs.Ease.backInOut)
					.call(yar.gotoAndStop, [0], yar);

			// Hover the character up and down
			createjs.Tween.get(yar, {loop:true})
					.to({y:20}, 500, createjs.Ease.quadInOut)
					.to({y:0}, 500, createjs.Ease.quadInOut);

			// Update the stage to reflect changes.
			createjs.Ticker.addListener(tweenStage);
		}

		function stopTweenDemo() {
			createjs.Tween.get(yar, {override:true});
			createjs.Ticker.removeListener(tweenStage);
			yar = null;
		}
	</script>
	<canvas id="tweenSampleCanvas" width="580" height="100" class="demo"></canvas>


<!-- snippet: tweenSample.js -->
// Tween the character back and forth, turning the eyes on and off.
createjs.Tween.get(yar, {loop:true})
	.wait(3000)
	.call(yar.gotoAndStop, [1], yar) // red eyes
	.wait(300)
	.to({x:450}, 1200, createjs.Ease.backInOut) // move
	.call(yar.gotoAndStop, [0], yar)
	.wait(3000)
	.call(yar.gotoAndStop, [1], yar)
	.wait(300)
	.to({x:50}, 1200, createjs.Ease.backInOut)
	.call(yar.gotoAndStop, [0], yar);

// Hover the character up and down
createjs.Tween.get(yar, {loop:true})
	.to({y:20}, 500, createjs.Ease.quadInOut)
	.to({y:0}, 500, createjs.Ease.quadInOut);

TweenJS 不仅限于显示对象属性。它可以缓动几乎任何东西。例如,游戏中的所有记分板都使用 SDK 中包含的 ScoreManager 库——该库有一个简单的 API:setScore()。一旦分数改变,TweenJS 就会在指定的持续时间内过渡分数,几乎不需要代码。

// Each time score is added, tween the value.
function addScore(score) {
	// Save the new score
	this.newScore = score;

	// Create a tween that will update the "displayScore", which
	// we use to display the changing number.
	var tween = createjs.Tween.get(this).to({displayScore:score}, 800);

	// For this example, set a local "scope" so the onChange
	// callback has something to refer to.
	var scope = this;

	// As the tween runs, it will call "onChange"
	tween.onChange = function(tween) {
		// Update the text instance with "displayScore".
		scope.text.text = scope.displayScore;
	}
}

// The "ScoreManager" abstracts the score formatting and tweening.
function killEnemy(points) {
	this.scoreManager.addScore(points);
}

 演示代码

<!-- ScoreManager demo -->
<script>
		window.addEventListener("load", initScoreManager, false);
		var scoreStage;
		var displayScore = 1000;
		var actualScore = 1000;
		function initScoreManager() {
			// Create the EaselJS stage
			scoreStage = new createjs.Stage(document.getElementById("scoreManagerCanvas"));
			initDemo("score", scoreStage, startScoreDemo, stopScoreDemo);
		}

		function startScoreDemo() {
			// Create a text field the score can live in
			var label = new createjs.Text("0", "40px Arial", "#f00");
			label.textBaseline = "top";
			scoreStage.addChild(label);

			// Create some simple instructions.
			var instructions = new createjs.Text("click the stage to add some points.", "12px Arial", "#999");
			instructions.textBaseline = "top";
			instructions.y = 45;
			scoreStage.addChild(instructions);

			// On stage mouse down, add some random points.
			scoreStage.onMouseDown = function() {
				var points = Math.random() * 1000 | 0;
				actualScore += points;
				createjs.Tween.get(window, {override:true})
						.to({displayScore:actualScore}, 3000, createjs.Ease.quartOut)
						.onChange = function() {
					var t = displayScore | 0;
					label.text = t;
				};
			}

			createjs.Ticker.addListener(scoreStage);
		}

		function stopScoreDemo() {
			createjs.Ticker.removeListener(scoreStage);
		}
	</script>
	<canvas id="scoreManagerCanvas" width="580" height="100"></canvas>

我们无法充分强调我们在开发 Atari Arcade 游戏时使用了多少 TweenJS。从《Super Breakout》中过渡图块,重置《Centipede》中的侏儒,到生成《Combat》中的坦克,TweenJS 提供了一种简单可靠的方法来随着时间的推移过渡值,并将结果绘制到每帧的屏幕上。而且由于它与 CreateJS Ticker 绑定,缓动可以随着游戏的暂停和恢复而同步。

其他一些例子包括

  • 在《Super Breakout》中初始化和闪烁球,包括在每次闪烁之间链式调用播放声音。
  • 在《Pong》和《Super Breakout》中过渡球拍的进出
  • 在《Pong》中得分时的烟花动画
  • 在新生命开始时初始化和闪烁《Asteroids》中的飞船
  • 《Centipede》中的关卡文本和分数弹出
  • 《Centipede》中的 Flea 动画和蘑菇修复过渡
  • 《Centipede》中的昼夜过渡
  • 触摸控件(操纵杆和推进器)位置重置
  • 《Lunar Lander》中的游戏结束延迟
  • 《Lunar Lander》中的旋转推进器

我们甚至在 Arcade Chooser 导航中使用它,您可以在其中选择游戏以将列恢复到其位置。

最后一个提示。由于我们的游戏都连接到一个单一的心跳,TweenJS 是延迟函数调用的绝佳替代品,而不是使用 setTimeout 之类的方法。这使我们能够拥有定时事件链,而无需担心用户暂停游戏等问题。

function killPlayer() {
	// Create a tween
	var tween = createjs.Tween.get(this.player).wait(100);
	for (var i= 0; i<5; i++) {
		// Tween in and out the player five times
		tween.to({alpha:1}, 200)
			.to({alpha:0}, 600, createjs.Ease.quadOut);
	}

	// After 2 seconds, show a dialog.
	// After another 3, hide it, and restart the game.
	createjs.Tween.get(this)
			.wait(2000)
			.call(this.showGameOverDialog, null, this)
			.wait(3000)
			.call(this.hideGameOverDialog, null, this)
			.call(this.restartGame, null, this);
}

PreloadJS - 覆盖您的资产

CreateJS 中的预加载是我们能够很大程度上隐藏起来的功能。使用资源清单(一个描述资源及其位置的简单 XML 文件),站点框架使用 PreloadJS 自动下载和管理游戏所需的资源。这包括脚本(注入到 HTML DOM 中)、音频、图像和 JSON 数据,这些数据将被传递到游戏中。

即使对我们项目中的人来说,PreloadJS 也是一场及时雨。预加载是 JavaScript 中最零散、最不可预测、最“需要回退方法”的技术之一,而该库将其全部封装在几个简单的 API 调用中。从开发者的角度来看,浏览器是否支持 XHR(XMLHttpRequests)、需要 ArrayBuffer 响应、晦涩的头部或其他有趣的解决方法并不重要。只需指向一些资源,然后开始。最新的浏览器会提供精细的数据和渐进式事件,而旧的浏览器则提供分块加载,但作为开发者,您甚至不需要考虑它。

一旦资源加载完毕,一个 JavaScript 对象将在游戏开始前被传递给游戏。可以使用游戏清单中提供的 ID 来访问资源,包括实际加载内容的引用,这些内容可以直接在游戏中用于游戏,而无需担心等待加载。

示例:在游戏中使用的已加载资源。

// The game manifest includes an image
var assets = [
	{id:"projectile", src:"images/projectile.png"}
];

// Inside the game, we just do this
function initialize(assets, stage) {
	var bitmap = new createjs.Bitmap(assets.projectile);
	stage.addChild(bitmap);
}

SoundJS - 动人的情感

游戏中的音频能让它更加完整。在制作《Centipede》时,这款游戏缺少我们在小时候玩我们父母那台旧旧的卡带机时感受到的那种恐慌感(嘿,我们还没老到那种程度)。添加声音和背景音乐能够唤起情感。

我们与 Washingtron Inc. 的杰出艺术家合作,为游戏创建了一套出色的声音,并立即实现了它们。事实上,大部分时间都花在了确定确切的播放时间,而不是如何播放。关于浏览器功能、所需格式和回退场景的任何猜测都已包含在内,任何棘手的情况都可以轻松设置为强制采用特定的方法。

<script>

	var audioDemoIndex = 0;
	var audioLabel;
	var audioStage;

	window.addEventListener("load", initAudioDemo, false);
	function initAudioDemo() {
		audioStage = new createjs.Stage(document.getElementById("audioDemoCanvas"));
		initDemo("audio", audioStage, startAudioDemo, stopAudioDemo)
	}
	function startAudioDemo() {
		audioDemoIndex = 0;
		audioLabel = new createjs.Text("Loading", "40px Arial", "#f00");
		audioStage.addChild(audioLabel);

		var loader = new createjs.PreloadJS(true);
		loader.installPlugin(createjs.SoundJS);
		loader.onComplete = playAudioLevel;
		loader.loadFile({id:"win", src:"assets/LevelComplete.mp3|assets/LevelComplete.ogg", data:3});

		audioLabel.text = "Loading...";
		audioStage.update();
	}

	function stopAudioDemo() {
		createjs.Tween.get(window, {override:true}).wait(10);
	}

	function playAudioDemo() {
		audioDemoIndex++;
		createjs.SoundJS.play("win");
		if (audioDemoIndex > 3) {
			audioLabel.text = "Game Over! Click to start again!";
			audioDemoIndex = 1;
		} else {
			audioLabel.text = "Click to Play Level " + audioDemoIndex;
		}
		audioStage.onMouseDown = playAudioLevel;
		audioStage.update();
	}

	function playAudioLevel() {
		audioStage.onMouseDown = null;
		audioLabel.text = "Playing Level " + audioDemoIndex + "...";
		createjs.Tween.get(window)
			.wait(1500).call(playAudioDemo);
		audioStage.update();
	}
</script>
<canvas id="audioDemoCanvas" width="580" height="100"></canvas>

SoundJS 透明地将音频预加载管理为 PreloadJS 的插件——一直到根据浏览器确定要加载的音频。这真的不能再简单了。

专业提示

  1. 标签加载音频虽然不如其他方法优雅——但支持更好,并且可以用于跨域或本地测试。
  2. 在游戏清单中,只定义一个 MP3。只要您包含了具有相同文件名(但扩展名不同)的其他格式的音频文件,SDK 就会根据用户的浏览器负责加载适当的文件。

 

目前我们在 HTML5 音频领域面临的唯一限制是 iOS。虽然可以播放音频(单个声音,点击后),但我们无法像在其他地方那样播放多个按需声音。不过,未来看起来一片光明——我们应该在 iOS 6 中获得 WebAudio——SoundJS 将准备好支持它!

Zoë - 捕捉瞬间

任何熟悉 gskinner.com 的人都知道我们来自强大的 Flash 背景。即使我们现在正在使用许多新技术,我们仍然认识到 Flash 的优势所在。我们的艺术家在 Illustrator 和 Photoshop 中进行资源设计——但对于时间线或角色动画来说,没有比经典的 Flash Pro 更好的工具了。

我们其他的热情之一是工具开发。如果你能花时间构建一个能自动完成任务的工具,为什么还要手动做 1000 次呢?Zoë 就是 2010 年我们与微软合作的首款 HTML5 游戏 Pirates Love Daisies 的答案。

Flash 中的动画可以轻松地导出为精灵表。Zoë 会记住您的历史记录,因此任何对精灵所做的更改都可以通过按一个(或两个)按钮重新导出。我们还提供了一个巧妙的脚本来帮助将资源合并到一个单一的、打包的精灵表中。

示例:《Centipede》的蜈蚣

主要的反派和游戏同名角色,可以用一个简单的精灵表来表示,其行走周期是水平和垂直移动的。最终的游戏精灵表与其余资源结合在一起,以及当蜈蚣死亡时播放的“死亡动画”。

但这还没完。Zoë 被设计成团队的一员,并导出精灵表 JSON 数据,这些数据很容易被 EaselJS 解析并渲染为动画精灵。太神奇了!

<script>
var spriteStage;
		var centipede;
		window.addEventListener("load", initSpriteDemo, false);
		function initSpriteDemo() {
			spriteStage = new createjs.Stage(document.getElementById("spriteCanvas"));
			initDemo("sprite", spriteStage, startSpriteDemo, stopSpriteDemo);
		}

		function startSpriteDemo() {
			// Define the JSON data
			var json = {
				"frames": {"regX": 0, "width": 52, "regY": 0, "count": 56, "height": 60},
				"animations": {"bodyWalk": [14, 26], "headWalk": [42, 54], "tailWalk": [0, 12], "bodyAltWalk": [28, 40]},
				"images": ["assets/centipede-article.png"]
			};

			// Create the spritesheet and pass in data.
			spriteSheet = new createjs.SpriteSheet(json);

			// Create a centipede container to control
			centipede = new createjs.Container();

			// Create 10 centipede pieces.
			for (var i= 0; i<10; i++) {
				var piece = new createjs.BitmapAnimation(spriteSheet);

				// Choose the right piece
				if(i==0) { piece.gotoAndPlay("headWalk"); }
				else if(i==9) { piece.gotoAndPlay("tailWalk"); }
				else if(i%2==1) { piece.gotoAndPlay("bodyWalk"); }
				else { piece.gotoAndPlay("bodyAltWalk"); }

				// The centipede is right-aligned.
				piece.x = -i*30 - 30;
				centipede.addChild(piece);
			}

			spriteStage.addChild(centipede);

			createjs.Ticker.addListener(spriteTick);
		}

		function stopSpriteDemo() {
			createjs.Ticker.removeListener(spriteTick);
		}

		// Each tick, move the centipede. Once it reaches the end, it loops back.
		function spriteTick() {
			centipede.x = (centipede.x + 8) % 880;
			spriteStage.update();
		}

	</script>
	<canvas id="spriteCanvas" width="580" height="100"></canvas>

游戏时间

CreateJS 为游戏开发者提供了使用他们舒适的架构方法的自由,同时维护了框架可以大规模控制的开发沙盒。如今,无插件的网络游戏和应用程序开发仍然是一片崎岖的土地,而 CreateJS 旨在降低进入门槛。

当我们把这种工作流程与出色的 Atari Arcade SDK 和游戏框架结合起来时,开发者就可以抛开浏览器兼容性和变通方法、繁琐的 Canvas API、多用户输入 API 和方法、预加载队列,而专注于游戏开发最精彩的部分:游戏玩法。

下一步?

  1. 阅读我们关于使用 SDK 进行游戏开发的文章。它深入探讨了游戏构建,包括我们在开发第一套游戏时使用的一些技巧。
    使用 Atari Arcade SDK 进行游戏开发
  2. 访问 Arcade GitHub 仓库下载 SDK、文档和快速入门指南。
    Atari Arcade SDK on GitHub
  3. 开始玩吧!

资源

这里有一些额外的 HTML5 游戏开发资源和参考资料。

 

© . All rights reserved.