Paladin 的 HTML5 Canvas 之旅
学习使用基本的 HTML5 Canvas 技能来动画化帝国骑士的精灵

目录
引言
本文旨在为 HTML5 & CSS3 初学者指南系列(Canvas 部分)贡献以下内容
- HTML5 Canvas 的概念整合到一个基础示例中:一个精灵(图像)沿着彩虹(渐变)路径向目标(文本)行进。
- 动力将加速:使用了一个轻松的主题——《帝国时代》(Age Of Empires,简称 AOE),这是微软发行的一款著名的历史模拟游戏。
- 将向进阶/有抱负的读者展示如何分几步将 2D 精灵扩展到 2.5D,正如原游戏中所示。
- 初学者可以继续进行简单的练习,答案已包含在可下载的示例源代码中。
事实上,我曾就这个示例为 HTML5 初学者进行过一个 2 小时的讲座。现在已进入 2014 年,HTML 5.0 将获得其推荐状态(主要约束浏览器厂商),我希望这个教程也能在您畅游充满前景的 Web 技术时对您有所帮助。
单张图片动画:Canvas 坐标、图片、定时器循环
上下文、坐标、图片渲染
将图片渲染到 HTML5 <canvas>
元素只需 5 行代码即可完成

- L1:“
canvas
” 是灰色字体,来源于我们核心元素 HTML5<canvas>
的id
,它在我们的 HTML 代码中声明如下
<canvas id="canvas" width="640" height="320" onclick="getCoords(event);">
<em>Your browser has no HTML5 canvas control support!</em>
</canvas>
请注意 getCoords(event)
事件处理程序,我们将使用它来接收发生在 canvas 上的单击/触摸事件,并将其解释为控制小骑士。canvas
元素调用。Image
对象,并将其路径设置为源图像文件。或者,您也可以使用 getElementById(id)
来引用一个预先声明的 HTML <img>
元素。canvas
的坐标系也从左上角开始,坐标为 (0,0),到右下角结束,坐标为 (canvas.width-1,canvas.height-1
)。该坐标系也用于图像渲染以及其他图形操作。
2 定时器循环
现在让我们添加一些超出 HTML5 范围但能让 Paladin “活起来”的东西。
简而言之,将使用 2 个定时器循环:一个用于数据计算/更新,另一个用于图形渲染。
数据更新循环
var timer_update = window.setInterval("update()", 1000/FrameRate);
function update() {
//... calculate the next move step towards destination, depending on sprite's speed (px/ms)
}
1000/FrameRate
是数据更新的时间间隔(以毫秒为单位)。由于简化起见,精灵仅从源直线移动到目标,下一个 (xSprite, ySprite
) 的计算将非常简单。图形渲染循环
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( callback ){
window.setTimeout(callback, 1000/FrameRateMax);
};
})();
...
requestAnimFrame(render);
...
function render() {
context.clearRect(0, 0, canvasWidth, canvasHeight);
context.drawImage(spriteImage, 0, 0, spriteWidth, spriteHeight,
xSprite-spriteWidth/2, ySprite-spriteHeight/2, spriteWidth, spriteHeight);
requestAnimFrame(render); //activate itself every time
}
window.requestAnimFrame
是目前脚本动画的事实标准,大多数浏览器都支持(而且“对电池友好”)。
在我们的渲染例程 (render()
) 中,您可以看到,在每个定时器脉冲上调用图像渲染 Canvas
API,并使用最新的 (xSprite, ySprite
) 坐标。别忘了在重绘之前移除旧精灵(为了简化,这里清除了整个 canvas
)。
运动路径:Canvas 路径、渐变、文本

如上图所示,到目前为止已实现了一个基于定时器循环的动画。Paladin 可能会抱怨我们只给他用了一张图片,让他看起来像被冻住了一样。我们将在下一节中优雅地平息他的愤怒。在此之前,让我们加快步伐,澄清其他有用的 Canvas
主题。
渐变、描边样式、路径
如何实现铺在草地上的彩虹路径?它是用下面的颜色 Gradient
定义的
var gradient = context.createLinearGradient(xSrc,ySrc,xDest,yDest);
gradient.addColorStop("0", "magenta");
gradient.addColorStop(".25", "blue");
gradient.addColorStop(".45", "green");
gradient.addColorStop(".65", "yellow");
gradient.addColorStop("1.0", "red");
从 (xSrc,ySrc
) 到 (xDest,yDest
) 创建一个线性渐变。gradient.addColorStop()
方法指定在哪个位置出现什么颜色。例如,(".25", "blue")
表示蓝色应出现在距离 (xSrc,ySrc
) 25% 的位置,而颜色停顿点之间的中间部分将自动计算。
然后将渐变应用于 canvas 的描边样式
context.strokeStyle = gradient;
//context.fillStyle = gradient;
Stroke(Line)
是绘制路径所必需的。注释掉的代码展示了如何将渐变应用于填充样式,而填充样式用于填充绘制。
context.lineWidth = 5;
context.lineCap = "round";
context.beginPath();
context.moveTo(xSrc, ySrc);
context.lineTo(xDest, yDest);
context.stroke();
//context.closePath();
上述代码说明了绘制 Canvas
Path 的通用例程。请注意,必须首先调用 beginPath()
来重置新的起点,只有当您想画一条回到起点的线时,才应调用 closePath()
。字体、文本、文本宽度
var text = "Run Run " + spriteName + "!";
var text_width = context.measureText(text).width;
context.fillStyle = "#ffff00";
//context.strokeStyle = "#ffff00";
var font_size = 15; //px
context.font = "italic bold "+font_size+"px Arial";
context.fillText(text, xDest - text_width/2, yDest + font_size/2);
//context.strokeText(text, xDest - text_width/2, yDest + font_size/2);
显示文本也不难。唯一的技巧是使用 context.measureText()
来测量文本宽度,以便计算正确的绘制起始点。结果是,文本将居中显示在我们 Paladin 的目的地。
(进阶)2.5D 精灵动画!:图集
"2.5D" 指的是对真实 3D 旋转、平移和缩放的模拟。
我真的希望您能以更逼真的方式享受控制我们 2D Canvas Paladin 的乐趣。这并不难,尤其是有了我提供的源代码模板。当然,想专注于基础研究的读者可以直接看下一节。

一个精灵的 8 个方向(每行一个方向)乘以每个方向大约 10 帧(每个动作 5 帧,2 种动作:stand
和 move
),总共 80 张图片将用于单个精灵。事实上,对于业余艺术家来说,新的 3D 建模,这是扩展示例时最难的部分。3DMax、Maya 或游戏地图编辑器的免费示例模型将很有帮助。
为了减少向服务器发起的 HTTP 请求,图片在 Web 开发中通常会被拼接。
<texture name="Paladin" file="Paladin.png"
maxFrameWidth="92" maxFrameHeight="98" offsetX="4" offsetY="0">
<frame name="stand01" x="0" y="0" width="92"
height="98" offsetX="0" offsetY="0"/>
<frame name="stand02" x="92" y="0" width="92"
height="98" offsetX="0" offsetY="0"/>
<frame name="stand03" x="184" y="0" width="92"
height="98" offsetX="0" offsetY="0"/>
......
<frame name="move01" x="460" y="0" width="82"
height="98" offsetX="10" offsetY="0"/>
<frame name="move02" x="542" y="0" width="82"
height="98" offsetX="10" offsetY="0"/>
<frame name="move03" x="624" y="0" width="82"
height="98" offsetX="10" offsetY="0"/>
......
</texture>
如何将特定帧绘制到 canvas 上?只需从 xml 数据中读取度量,然后调用 context.drawImage(image,sx,sy,sw,sh,dx,dy,dw,dh)
方法。
请记住,对于每种精灵,我们使用相同的尺寸(maxFrameWidth,maxFrameHeight
)来绘制它。然而,为了节省空间,有些帧可能比其他帧小。在这种情况下,(offsetX, offsetY
) 将用于指定从哪个相对位置绘制较小的帧。
3.Ajax/XMLHttpRequest, OO 设计:加载图集图片,加载 & 解析图集数据,每个帧学会绘制自己
我的示例的完整版本按顺序将所有内容连接在一起,如下所示
atlasImage = new Image();
atlasImage.src = spriteName + ".png";
atlasImage.onload = function()
{
var loadPath = "atlas.xml";
var client = new XMLHttpRequest();
client.onreadystatechange = atlasHandler;
client.open("GET", loadPath, false); //false = sync-style
client.send();
}
图集图片将通过将其路径应用于 Image
对象来加载。加载图片后(atlasImage.onload
事件),将使用 Ajax/XMLHttpRequest
回调方法加载图集 XML 数据。图集 XML 数据将在回调处理程序 atlasHandler()
中使用 DOM API(如 getElementsByTagName()
)进行解析,这里将省略。
解析结果将是一个 atlasMap
,它将帧名称映射到相应的 AtlasFrame
类,如下所示
function AtlasFrame()
{
this.m_x;
this.m_y;
this.m_width;
this.m_height;
this.m_offsetX;
this.m_offsetY;
this.m_name;
this.load = function(elem) {
//... xml element --> instance variables
}
this.render = function(x, y) {
context.drawImage(atlasImage, this.m_x, this.m_y + currDirection * atlasTexture.m_maxFrameHeight,
this.m_width, this.m_height,
this.m_offsetX+x-atlasTexture.m_maxFrameWidth/2, this.m_offsetY+y-atlasTexture.m_maxFrameHeight/2,
this.m_width, this.m_height);
}
};
不同的路径通往同一个地方,我们在 AtlasFrame.render()
中再次遇到 context.drawImage()
。这次,每个动画帧都负责自己的渲染(在 Canvas
上)。因此,作为这些帧的集合,Animation
类可以在每个图形渲染定时器脉冲上轻松地渲染自身。
有了以上知识,此外,通过在我的示例源代码(总共 635 行)中搜索“Instruction##
”,您可以轻松找到更新 8 个地方,将您自己喜欢的精灵和故事放到网上!
(基础)面板:练习时间
练习主题

迷你面板包含了几乎所有提到的 Canvas
技巧,答案可以在我提供的完整源代码版本中找到。
提示:然而,这两个缩略图并不是真正的Canvas 图片。不要在它们上面浪费时间 :) 示例仅展示了如何将普通的 HTML 元素叠加到 HTML5 Canvas 上。
彩蛋:1.您可以通过单击此面板来打开/关闭彩虹路径跟踪 2.尝试单击剑士缩略图。
结论
我在公司进行的 2 小时 HTML5 讲座中涵盖的主题还包括离线 WebApp(通过设置 .appcache 文件)以及将示例嵌入 Android Native App(使用 WebKit)。有兴趣的读者可以按照括号中的关键词扩展这些功能。
最后,除了模拟 2.5D 精灵,您还可以通过在 HTML5 <canvas>
元素上使用 WebGL 来渲染真实的 3D 图形。
等等,我可以在线试试吗?
当然,我将 2.5D 版本 在线(用户名/密码=test/test)。然而,基础版本可以直接在任何主流浏览器上运行,只需将解压后的 basic.html
拖入其中即可。如果您有任何评论、建议或改进意见,请随时在此处发布!~
有用链接
- Canvas 语法参考 @W3CSchools
- 我参考过的 2D HTML5 动画,在完成 2.5D 动画之前
- 高级、严肃的示例,采用严格的面向对象 JavaScript:Ultimate Tower Defense @CodeProject
历史
- 2014 年 4 月 25 日:第 1 版