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

Paladin 的 HTML5 Canvas 之旅

starIconstarIconstarIconstarIconstarIcon

5.00/5 (7投票s)

2014年4月26日

CPOL

7分钟阅读

viewsIcon

25470

downloadIcon

676

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

HTML5 Canvas animation

目录

引言

本文旨在为 HTML5 & CSS3 初学者指南系列(Canvas 部分)贡献以下内容

  • HTML5 Canvas 的概念整合到一个基础示例中:一个精灵(图像)沿着彩虹(渐变)路径向目标(文本)行进。
  • 动力将加速:使用了一个轻松的主题——《帝国时代》(Age Of Empires,简称 AOE),这是微软发行的一款著名的历史模拟游戏。
  • 将向进阶/有抱负的读者展示如何分几步将 2D 精灵扩展到 2.5D,正如原游戏中所示。
  • 初学者可以继续进行简单的练习,答案已包含在可下载的示例源代码中。

事实上,我曾就这个示例为 HTML5 初学者进行过一个 2 小时的讲座。现在已进入 2014 年,HTML 5.0 将获得其推荐状态(主要约束浏览器厂商),我希望这个教程也能在您畅游充满前景的 Web 技术时对您有所帮助。

单张图片动画:Canvas 坐标、图片、定时器循环

上下文、坐标、图片渲染

将图片渲染到 HTML5 <canvas> 元素只需 5 行代码即可完成

Canvas Coordinates, Images
  • 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 上的单击/触摸事件,并将其解释为控制小骑士。
  • L2:检索 canvas 的 2D 图形 上下文。实际上,所有图形操作都将通过上下文而不是通过 canvas 元素调用。
  • 注意:可以在一个 Web 应用程序中使用多个 canvas(以及它们一对一相关的上下文)。
  • L3-L4:创建一个 Image 对象,并将其路径设置为源图像文件。或者,您也可以使用 getElementById(id) 来引用一个预先声明的 HTML <img> 元素。
  • 顺便说一下,“Paladin” 是 AOE 游戏中经验最丰富的骑士单位的称号 :)。
  • L5:从源图像目标 Canvas 的映射(渲染)非常直接。
  • 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 路径、渐变、文本

Canvas Paths, Gradients, Text

如上图所示,到目前为止已实现了一个基于定时器循环的动画。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 的乐趣。这并不难,尤其是有了我提供的源代码模板。当然,想专注于基础研究的读者可以直接看下一节。

1. 图集:动画图片拼接

Atlas: paladin in 8 directions

一个精灵的 8 个方向(每行一个方向)乘以每个方向大约 10 帧(每个动作 5 帧,2 种动作:standmove),总共 80 张图片将用于单个精灵。事实上,对于业余艺术家来说,新的 3D 建模,这是扩展示例时最难的部分。3DMax、Maya 或游戏地图编辑器的免费示例模型将很有帮助。
为了减少向服务器发起的 HTTP 请求,图片在 Web 开发中通常会被拼接。

2.atlas.xml:动画数据,每个命名帧的度量

<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 个地方,将您自己喜欢的精灵和故事放到网上!

(基础)面板:练习时间

练习主题

panel for practice

迷你面板包含了几乎所有提到的 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 拖入其中即可。如果您有任何评论、建议或改进意见,请随时在此处发布!~

有用链接

历史

  • 2014 年 4 月 25 日:第 1 版
© . All rights reserved.