HTML5专家David Geary对HTML5 的 2D 视频游戏详细剖析思路

图1显示的是滚动背景以及对帧速率的监视

图2显示的是Canvas 坐标系(默认)


图4是完全相同的边缘实现平滑的过渡(左:右边缘;右:左边缘)

从右侧滚动到左侧:半透明区域代表在屏幕外的图像部分
二、具体的实现步骤好coding
1、使用drawRunner()函数把跑步者给绘制出来
- function drawRunner() {
- context.drawImage(runnerImage, // image
- STARTING_RUNNER_LEFT, // canvas left
- calculatePlatformTop(runnerTrack) - RUNNER_HEIGHT); // canvas top
- }
分析:其实drawRunner函数中主要是将三个参数(一个图像、左侧坐标和顶部坐标)向drawImage()传递,其中顶部坐标由跑步者所驻留的平台决定,而左侧坐标则是一个常数。
2、通过drawBackground()背景函数绘制背景,drawPlatforms()函数绘制平台
- function drawBackground() {
- context.drawImage(background, 0, 0);
- }
- var platformData = [
- // Screen 1.......................................................
- {
- left: 10,
- width: 230,
- height: PLATFORM_HEIGHT,
- fillStyle: 'rgb(150,190,255)',
- opacity: 1.0,
- track: 1,
- pulsate: false,
- },
- ...
- ],
- ...
- function drawPlatforms() {
- var data, top;
- context.save(); // Save the current state of the context
- context.translate(-platformOffset, 0); // Translate the coord system for all platforms
- for (var i=0; i < platformData.length; ++i) {
- data = platformData[i];
- top = calculatePlatformTop(data.track);
- context.lineWidth = PLATFORM_STROKE_WIDTH;
- context.strokeStyle = PLATFORM_STROKE_STYLE;
- context.fillStyle = data.fillStyle;
- context.globalAlpha = data.opacity;
- context.strokeRect(data.left, top, data.width, data.height);
- context.fillRect (data.left, top, data.width, data.height);
- }
- context.restore(); // Restore context state saved above
- }
分析:drawBackground() 函数在画布的 (0,0) 绘制背景图像。稍后,我会在本文中修改该函数,以便滚动背景。而绘制平台(它们不是图像)需要更广泛地使用 Canvas API。多用途的 drawImage() 方法您可以使用 Canvas 2D 上下文的 drawImage() 方法在画布内的任何地方绘制一个完整的图像,或图像内的任何矩形区域,有选择地沿着路线缩放图像。除了图像外,您还可以利用 drawImage() 绘制另一个画布或一个 video 元素当前帧的内容。这只是其中一个方法,但 drawImage() 还有助于便利地实现有趣的或者难以实现的应用程序(如视频编辑软件)。
其中的 JavaScript 定义一个名称为 platformData 的数组。该数组中的每个对象代表着描述一个独立平台的元数据。drawPlatforms() 函数使用 Canvas 上下文的 strokeRect() 和 fillRect() 方法来绘制平台矩形。这些矩形的特征存储在 platformData 数组内的对象中,用于设置上下文的填充风格和 globalAlpha 属性,该属性设置您之后在画布上绘制的任何图形的不透明度。调用 context.translate() 将画布的坐标系(如图 2 所示)在水平方向平移指定数量的像素。该平移和属性设置是临时的,因为这些操作是在 context.save() 和 context.restore() 调用之间执行的。
3、使用setInterval()和requestAnimationFrame()对动画的实现
- setInterval( function (e) { // Don't do this for time-critical animations
- animate(); // A function that draws the current animation frame
- }, 1000 / 60); // Approximately 60 frames/second (fps)
- function animate(time) { // Animation loop
- draw(time); // A function that draws the current animation frame
- requestAnimationFrame(animate); // Keep the animation going
- };
- requestAnimationFrame(animate); // Start the animation
分析:从许多方面来看,HTML5 是程序员的乌托邦。没有专用的 API,开发人员使用 HTML5 在无处不在的浏览器中实现跨平台运行的应用程序。规范发展迅速,不断采用新技术,同时改进现有的功能。
Polyfill:面向未来的编程过去,大多数跨平台软件都在最低的共同点实现。Polyfill 彻底颠覆了这一概念,它让您能够访问高级特性(如果它们可用),并在必要时回退到一个能力较低的实现。
然而,新技术要实行规范,往往是通过特定浏览器现有的功能来实现的。浏览器厂商通常为这样的功能添加了前缀,使它们不会干扰其他浏览器的实现;例如,requestAnimationFrame() 最初被 Mozilla 实现为 mozRequestAnimationFrame()。然后 WebKit 实现了它,将其函数命名为 webkitRequestAnimationFrame()。最后,W3C 将它标准化为 requestAnimationFrame()。
供应商提供了对前缀实现以及标准实现的不同支持,这使得新功能的使用变得非常棘手,所以 HTML5 社区发明了一种被称为 polyfill 的东西。Polyfill 针对特定功性确定浏览器的支持级别,如果浏览器已经实现了该功能,您就可以直接访问它,否则,浏览器会向您提供一个暂时尽量模仿标准功能的实现。Polyfill 易于使用,但实现起来可能比较复杂。清单 6 演示了 requestAnimationFrame() 的一个 polyfill 的实现:
- requestNextAnimationFrame() polyfill
- // Reprinted from Core HTML5 Canvas
- window.requestNextAnimationFrame =
- (function () {
- var originalWebkitRequestAnimationFrame = undefined,
- wrapper = undefined,
- callback = undefined,
- geckoVersion = 0,
- userAgent = navigator.userAgent,
- index = 0,
- self = this;
- // Workaround for Chrome 10 bug where Chrome
- // does not pass the time to the animation function
- if (window.webkitRequestAnimationFrame) {
- // Define the wrapper
- wrapper = function (time) {
- if (time === undefined) {
- time = +new Date();
- }
- self.callback(time);
- };
- // Make the switch
- originalWebkitRequestAnimationFrame = window.webkitRequestAnimationFrame;
- window.webkitRequestAnimationFrame = function (callback, element) {
- self.callback = callback;
- // Browser calls the wrapper and wrapper calls the callback
- originalWebkitRequestAnimationFrame(wrapper, element);
- }
- }
- // Workaround for Gecko 2.0, which has a bug in
- // mozRequestAnimationFrame() that restricts animations
- // to 30-40 fps.
- if (window.mozRequestAnimationFrame) {
- // Check the Gecko version. Gecko is used by browsers
- // other than Firefox. Gecko 2.0 corresponds to
- // Firefox 4.0.
- index = userAgent.indexOf('rv:');
- if (userAgent.indexOf('Gecko') != -1) {
- geckoVersion = userAgent.substr(index + 3, 3);
- if (geckoVersion === '2.0') {
- // Forces the return statement to fall through
- // to the setTimeout() function.
- window.mozRequestAnimationFrame = undefined;
- }
- }
- }
- return window.requestAnimationFrame ||
- window.webkitRequestAnimationFrame ||
- window.mozRequestAnimationFrame ||
- window.oRequestAnimationFrame ||
- window.msRequestAnimationFrame ||
- function (callback, element) {
- var start,
- finish;
- window.setTimeout( function () {
- start = +new Date();
- callback(start);
- finish = +new Date();
- self.timeout = 1000 / 60 - (finish - start);
- }, self.timeout);
- };
- }
- )
- ();
4、html 游戏进入循环
- <html>
- ...
- <body>
- ...
- <script src='js/requestNextAnimationFrame.js'></script>
- <script src='game.js'></script>
- </body>
- </html>
- var fps;
- function animate(now) {
- fps = calculateFps(now);
- draw();
- requestNextAnimationFrame(animate);
- }
- function startGame() {
- requestNextAnimationFrame(animate);
- }
分析:既然图形和动画的先决条件已经得到满足,那么现在是时候让 Snail Bait 动起来了。首先,我在游戏的 HTML 中让 requestNextAnimationFrame() 包含 JavaScript。startGame() 函数由背景图像的 onload 事件处理器调用,该函数通过调用 requestNextAnimationFrame() polyfill 启动游戏。在绘制游戏的第一个动画帧时,浏览器会调用 animate() 函数。
5、计算 fps 并更新 fps 元素及滚动背景并设置背景位移
- var lastAnimationFrameTime = 0,
- lastFpsUpdateTime = 0,
- fpsElement = document.getElementById('fps');
- function calculateFps(now) {
- var fps = 1000 / (now - lastAnimationFrameTime);
- lastAnimationFrameTime = now;
- if (now - lastFpsUpdateTime > 1000) {
- lastFpsUpdateTime = now;
- fpsfpsElement.innerHTML = fps.toFixed(0) + ' fps';
- }
- return fps;
- }
- var backgroundOffset; // This is set before calling drawBackground()
- function drawBackground() {
- context.translate(-backgroundOffset, 0);
- // Initially onscreen:
- context.drawImage(background, 0, 0);
- // Initially offscreen:
- context.drawImage(background, background.width, 0);
- context.translate(backgroundOffset, 0);
- }
- var BACKGROUND_VELOCITY = 42, // pixels / second
- bgVelocity = BACKGROUND_VELOCITY;
- function setBackgroundOffset() {
- var offset = backgroundOffset + bgVelocity/fps; // Time-based motion
- if (offset > 0 && offset < background.width) {
- backgroundOffset = offset;
- }
- else {
- backgroundOffset = 0;
- }
- }
分析:帧速率只是自上一个动画帧开始计算的时间量,所以您也可以认为它是 frame per second(帧每秒)而不是 frames per second(每秒的帧数),这使得它不太像是一个速率。您可以采用更严格的方法,在几个帧中保持平均帧速率,但我还没有发现这样做的必要性,事实上,自最后一个动画帧起所用的时间就正是我在 基于时间的运动 中所需要的。
执行任务的速率不同于动画速率。如果我在每一个动画帧都更新帧/秒值,则无法读取速率,因为它总是在不断变化;我将该设置改为每秒更新一次。设置好了游戏循环和帧速率之后,我现在就准备开始滚动背景了。setBackground() 函数在水平方向平移画布上下文 -backgroundOffset 像素。如果 backgroundOffset 是正数,那么背景会向右侧滚动;如果它是负数,那么背景会向左侧滚动。
在平移背景之后,drawBackground() 绘制了两次背景,然后将上下文平移回它在调用 drawBackground() 之前的位置。
一个看似琐碎的计算仍然保留:计算 backgroundOffset,这决定了为每个动画帧将画布的坐标系统平移多远。虽然该计算本身确实是琐碎的,但它具有重要的意义,所以我接下来将会讨论它。
6、设置背景位移、设置背景位移及draw() 函数
- var BACKGROUND_VELOCITY = 42, // pixels / second
- bgVelocity = BACKGROUND_VELOCITY;
- function setBackgroundOffset() {
- var offset = backgroundOffset + bgVelocity/fps; // Time-based motion
- if (offset > 0 && offset < background.width) {
- backgroundOffset = offset;
- }
- else {
- backgroundOffset = 0;
- }
- }
- var PLATFORM_VELOCITY_MULTIPLIER = 4.35;
- function setPlatformVelocity() {
- // Platforms move 4.35 times as fast as the background
- platformVelocity = bgVelocity * PLATFORM_VELOCITY_MULTIPLIER;
- }
- function setPlatformOffset() {
- platformOffset += platformVelocity/fps; // Time-based motion
- }
- function setOffsets() {
- setBackgroundOffset();
- setPlatformOffset();
- }
- function draw() {
- setPlatformVelocity();
- setOffsets();
- drawBackground();
- drawRunner();
- drawPlatforms();
- }
分析:draw() 函数设置了平台速度,并为背景和平台设置了位移。然后,它绘制背景、跑步者和平台。Snail Bait 的游戏循环。该循环包括一个 animate() 函数,在需要绘制游戏的下一个动画帧时,浏览器会调用该函数。然后,该 animate() 函数调用一个 draw() 函数来绘制下一个动画帧。
David Geary简介 :他是 JSTL 1.0 和 JSF 1.0/2.0 专家组的成员,他还是 GWT Solutions 一书的作者。David 经常在各大会议和用户组发表演讲。是作家、演讲家以及顾问,也是 Clarity Training, Inc. 的总裁,他指导开发人员使用 JSF 和 Google Web Toolkit (GWT) 实现 Web 应用程序。与人合作编写了 Sun 的 Web Developer 认证考试的内容,并且为多个开源项目作出贡献,包括 Apache Struts 和 Apache Shale。David 的 Graphic Java Swing 一直是关于 Java 的畅销书籍,而 Core JSF(与 Cay Horstman 合著)是关于 JSF 的畅销书。他从 2003 年开始就一直是 NFJS tour 的定期演讲人,并且在 Java University 教授课程,三次当选为 JavaOne 之星。
本文来源 我爱IT技术网 http://www.52ij.com/jishu/10.html 转载请保留链接。
- 评论列表(网友评论仅供网友表达个人看法,并不表明本站同意其观点或证实其描述)
-
