Canvas2D移动web游戏开发 – 基础

简介

本文的主要目的是让你熟悉Tizen平台移动web游戏的开发。 我想向你介绍关于Canvas 2D API的基本知识,并且好的游戏架构足以在Tizen上创建一个功能完整的HTML5游戏。 示例应用程序 – 本文附带的Earth Guard 0.0.1与Tizen SDK 2.0.0发布版相兼容。

如何使用应用程序?

在附带的示例应用程序 –Earth Guard 0.0.1中,唯一的用户输入机制是键盘。 所以我们建议你在Tizen模拟器上运行这个应用,并使用上/下/左/右和空格键来控制玩家飞船。 使用陀螺仪实现移动设备应用程序控制,在“保持你的Tizen Web应用程序的高便携性”一文中描述。

我是否需要任何框架来开发移动web游戏?

有许多JavaScript游戏框架。 多数在2D环境下使用Canvas,少数人使用WebGL。 主要原因是,Canvas 2D API 的简单性,及二维空间标准坐标和你在Cancas 2D上绘画的方式之间的自然关联。 另外WebGL主要设计用于通过透视及复杂的阴影表现3D图像。 欲了解更多关于WebGL的主题,请参阅Tizen自定义3D图形文章。 了解Canvas 2D环境基本信息请访问:Tizen自定义2D图形

有众多适用于web应用的游戏框架:Cocos2D-HTML5QuintusCraftyJS等,此处仅列举一二 。 幸运的是,你不需要了解他们,便可以创建那些可以运行于包括Tizen在内的HTML5友好平台上的高度互动的掌上游戏。 你唯一需要了解的是,如何使用Canvas 2D API以及如何设计一个易于维护的游戏类架构,这些都将在本文中介绍。

Canvas2D介绍

Canvas 2D声明和特征检测

仅需要对Canvas 2D环境稍作了解便可以进行Tizen web游戏开发。 你需要做的第一件事是在你的DOM结构中创建一个canvas元素。 请参考示例应用程序中的index.html文件。

<canvas id='game' width='720' height='1280'></canvas>

你需要给canvas元素设置适当的宽度和高度。 在我们的示例中,canvas适用于高清分辨率。

之后你便可以在JavaScript应用程序中引用你的canvas实例。 在引用之前你应该进行特征检测以确认你的浏览器是否支持2D环境下的Canvas API。 幸运的是Tizen内嵌WebKit浏览器,完全支持2D环境下的Canvas API。 所有只有当你决定将应用程序发布至桌面浏览器时才需要进行这项验证。 这是一个非常值得推荐的方法,因为Earth Guard游戏就可以在桌面浏览器轻松启动。 关于Canvas2D特征检测请参阅./js/game.js文件中的游戏模块:

checkEnviroment : function() {
    var canvas = document.getElementById('game');
    var ctx = canvas.getContext && canvas.getContext('2d');
    if (!ctx) {
        alert("Please upgrade your browser!");
        return false;
    }
    ...
    ...
    return true;
},

当验证成功后,你可以在你的canvas变量中引用canvas元素。

用Canvas2D处理sprite图片

在成功引用画布之后,我们应当考虑如何在我们的应用程序中保存图像资源。 最佳的方法是将所有游戏相关的图像放在一个sprite图片中。 了解关于sprite图片的信息请访问:http://www.w3schools.com/css/css_image_sprites.asp

Canvas 2D API可以非常简单的对sprite图片进行操作。 想要加载自定义的sprite图片并利用Canvas API对其操作,你需要遵循下面的步骤:

  1. 新建一个图片对象实例。
  2. 为图片加载事件指定回调。 在这个回调函数中你可以安全的引用之前创建的图片对象实例。 具体实现方法将在稍后描述。
  3. 设置图片src属性指定sprite图片路径。

Canvas 2D API提供了如下接口:

canvas.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);

参数说明:
image – 引用之前创建的图片对象实例
sx, sy, sWidth, sHeight – 源图片开始剪切的左上角坐标及剪切宽度、高度。
dx, dy, dWidth, dHeight – 在画布上放置源图片的位置
 

在Earth Guard中我们创建了一个单独的模块用来处理sprite图片(spriteSheet in ./js/modules/spriteSheet.js)。 在该模块中一个完整的sprite图片结构体定义如下:

ship : {
    sx : 0, // top left corner x coordinate
    sy : 0, // top left corner y coordinate
    w : 37, // sprite width
    h : 42, // sprite height
    frames : 1 // frame number
},

由于一个元素中的图像均平铺放置在sprite图片中,为此我们提供了一个画面属性,使得我们可以针对一个元素仅处理sprite图片中的一个指定的画面。 可以看到下面的爆炸元素具有12个画面。 所有其他元素仅有一个画面。

图 1:Earth Guard初始sprite页

图 1:Earth Guard初始sprite页

现在你应该对sprite图片有了大致的了解并且知道在JSON中应该如何定义它。  下一步是了解spriteSheet模块,利用它可以处理sprites中的多个元素并在画布上绘制它们。

var spriteSheet = function() {
    var sprites = {
    ship : {
        sx : 0, // top left corner x coordinate
        sy : 0, // top left corner y coordinate
        w : 37, // sprite width
        h : 42, // sprite height
        frames : 1 // frame number
    }
    };
    var spriteSource = 'images/sprites.png';
    var image = new Image();
    return {
        load : function(callback) {
            image.onload = callback;
            image.src = spriteSource;
        },
        draw : function(ctx, sprite, x, y, frame) {
            var s = sprites[sprite];
            if (!frame)
                frame = 0;
            ctx.drawImage(image, s.sx + frame * s.w, s.sy, s.w, s.h, Math
                    .floor(x), Math.floor(y), s.w, s.h);
        }
    };
}();

SpriteSheet模块提供了两种方法:load和draw。 load方法用于指定事件加载时的回调函数以及图片对象的src属性。 请记住只有当浏览器加载完图片后你才可以使用draw方法,所以基本上你只可以在回调函数中或者在回调函数调用完以后使用它。 Earth Guard的spriteSheet.load方法在game.initialize方法中被调用。 当sprite图片加载完成后我们可以安全的使用spriteSheet draw方法。

如何进行Tizen游戏开发?

当你开始移动web游戏开发时需要回答的第一个问题是,哪些类型的抽象需要映射到你的游戏元素中,诸如敌人、子弹、导弹等。 它既可以是DOM元素也可以绘制在2D Canvas平面上。 第一种类型通常被称为Retain模式,该模式下浏览器可以追踪对象的位置及层次关系。 你不需要从头绘制每一个画面,你只需要调整发生变化的元素,浏览器将负责正确的渲染所有东西。 另外还有Immediate模式可以让你控制在平面上绘制所有的像素。 你可以控制对象的层次结构 – 这意味着由你来决定要将哪些对象绘制在其他对象上方。 在这个模式下对象的位置发生变化时不需要重新绘制。 那种模式更好呢?一般来说,当你需要多个对象间互相作用,例如触摸控制,使用Retain模式更好,因为它可以方便的指定所有对象的事件处理。 另一方面,如果你使用一些简单的输入机制来控制你的应用程序,用Immediate模式会更好。

我们的示例游戏使用了Immediate模式,因为示例游戏中只采用了陀螺仪/键盘控制方式并且用户不能直接与对象进行触摸交互。

游戏结构 – 三个主要对象

无论在任何游戏中你都需要三个主要对象:一个Game对象将所有东西绑在一起,一个GameBoard对象处理sprites冲突并将它们组织起来以及一个SpriteSheet对象允许从一个给定的文件中加载一个sprite用于在屏幕上显示sprite的选中部分。

Immediate模式概念 – 游戏循环

在Immediate模式下应用程序的入口为游戏循环,其中:

  1. 画布被清空
  2. 计算新的对象位置
  3. 所有对象绘制在第2步中计算的位置上
  4. 事件循环被释放,释放的时间间隔等于T(毫秒)

图 2:游戏循环示意图

图 2:游戏循环示意图

超快速硬件理想情况下,我们假设1-3步不消耗时间,所以T1=0。 这就是为什么我们需要释放(第4步)时间周期T2=T的整个JavaScript事件循环。在这段释放时间内,我们的应用程序可以处理其他事件,例如:键盘事件,触摸事件,陀螺仪事件等。 你要选择合适的T时间值,因为游戏的FPS(每秒帧速率)依赖于它。 FPS是游戏绘制平面刷新频率。 它可以用下面的方式计算:FPS=1/T。通常开发人员知道他需要为游戏设置的FPS数值。 例如,在Earth Guard中该数值设置为40fps。 如果FPS已定义你可以计算出T=1/FPS。 所以在我们的例子中T=0.025秒=25毫秒。 计算完成后你可以根据指定的数值创建一个游戏循环。 下面的代码只是一段伪代码,直接复制/粘贴将不会起作用。 它仅用于演示游戏循环的概念。

var fps = 40; // sample fps set to 40
var T = 1 / (fps / 1000); // period needs to be defined in msec
var dt = T / 1000;
var loop = function() {
    background.draw(ctx); // clear canvas (1st point)
    obj.step(dt); // calculate new object position (2nd point)
    obj.draw(ctx); // draw object taking into account new coordinates (3rd point)
    setTimeout(loop, T); // recursive call of loop method after given period (4th point)
};
loop();

使用自适应游戏循环来优化游戏的硬件性能

对于基本的游戏循环的概念我们如之前章节中描述的那样假设1-3步不消耗时间。 实际上对于真实的设备而言这是不可能的,因为渲染和新位置计算都是需要消耗时间的。 这个时间长度由设备性能决定。 如果性能良好(例如桌面浏览器),T1时间可以几乎等于0。 如果是移动设备例如Tizen,我们需要将T1时间纳入考虑,以创建合适的游戏循环。 在自适应游戏循环中,我们释放主程序事件循环的时间不是T,而是T2=T-T1。 这种方法的好处是,设备性能突然下降时用户将不会察觉,因为我们从开发者定义的周期(T)中减去了延迟时间(T1)并在这个新的周期内释放主事件循环。

图 3:游戏自适应循环示意图

图 3:游戏自适应循环示意图
 

下面的代码表示一个具有5毫秒阈值的自适应游戏循环。

var fps = 40; // sample fps set to 40
var T = 1 / (fps / 1000); // period needs to be defined in msec
var dt = T / 1000;
var loop = function() {
    var ta = new Date();
    background.draw(ctx); // clear canvas (1st point)
    obj.step(dt); // calculate new object position (2nd point)
    obj.draw(ctx); // draw object taking into account new coordinates (3rd point)
    var tb = new Date();
    var T1 = tb-ta; // calculate T1 duration
    var T2 = T - T1 // calculate T2 that will be used for setTimeout
    if(T2<5) // in case the device performance is so poor that T2 time is less then 5 msec, leave 5msec time to release the event loop to not block the application
        T2 = 5;
    setTimeout(loop, T2); // recursive call of loop method after given period (4th point)
};

基本游戏架构

基本的游戏架构包括下面的类和模块:

  1. Game模块 - 它的主要功能是初始化Earth Guard的游戏引擎,执行游戏循环并提供改变游戏板的机制。
  2. GameBoard类 – 它的主要功能是整合游戏逻辑元素例如:飞船,导弹等。 它能够方便地添加/删除板上的元素以及遍历元素的属性。
  3. spriteSheet模块 – 保证所有sprite处理相关的功能。 由Sprite类进行调用。
  4. Sprite类 – 提供在Canvas2D上绘制元素图片的功能及管理当前元素的sprite图片。
  5. 玩家飞船,敌人,玩家导弹均继承自Sprite类。 所以他们不需要关心元素的绘制。 这些类只负责处理元素的专用逻辑及计算新的位置。

图 4:基本Earth Guard类示意图

图 4:基本Earth Guard类示意图

总结

本文简要的介绍了应该使用哪些方法进行移动web游戏开发。 你应该已经了解了游戏循环和自适应游戏循环以及基本的web游戏架构。 本文附带的示例程序叫做Earth Guard v. 0.0.1。

这是三篇系列文章中的第一篇。 如果你想了解更多请参阅:Canvas2D移动web游戏开发 – 实现文档。

文件附件: