当前位置 :
html5 canvas这是一个简单且简洁的贪食蛇游戏。
0粉丝/0关注
举报原因:
&&垃圾广告
&&淫秽色情
&&虚假中奖
&&敏感信息
&&人身攻击
&&骚扰他人
下载该资源用户也下载了
使用邮箱登录17素材
已连续签到1天,签到3天将获得积分VIP1天51CTO旗下网站
在游戏中发挥HTML5 Canvas的潜能
目前, 支持HTML 5的浏览器和Windows 8 Metro成为了游戏开发的候选方案。利用canvas(画布)可以实现硬件加速,你可以在上面绘制游戏的内容,通过一些技巧可以达到每分钟60帧的渲染速度。
作者:feijun来源:Web App Trend| 12:18
在游戏中,fluidity(流畅性)这一概念非常重要,因为好的流畅性能带给玩家更好的体验。
这篇文章的主旨在于教你一些技巧,让你可以最大程度地发挥HTML5 canvas的潜能。
我将通过一个例子来表达我要讲的内容。这个例子是2D tunnel effect(2D隧道效应),它是我为Coding4Fun 会议写的(我参加了在法国举办的TechDays 2012)。
早在80年代,当我还是一个年轻的demomaker的时候曾受到一些Commodore AMIGA代码的启发,从而写了这个2D tunnel effect。
经过不断地改进,现在它仅使用了canvas和Javascript(最初的代码是基于68000汇编的):
完整的代码可以从这里获得:
这篇文章的目的不是讲解该程序的开发过程,而是让你通过优化已有的代码来达到一个实时性的效果。
使用off-screen canvas(离屏画布)来读取图片数据
我想讲的第一点是怎样使用canvas来读取图片数据。实际上,任何一个游戏都需要图形来显示游戏界面和背景。canvas有一个非常有用的画图方法:drawImage&。这个功能可以用来绘制游戏界面,通过它你可以定义起始和目的区域。
但是有时候光使用它是不够的,比如你想要在源图像上实现一些特效,或者源图像不是一个简单的位图而是一个复杂的资源(如地图)。
在这些情况下,你需要访问到图片的内部数据。但是Image标签无法读到这些数据,这时候就该canvas上场了。
事实上,每当你需要从图片中读取内容的时候,你都可以使用off-screen canvas。也就是说,当你导入一张图片的时候,你只需要将它渲染到canvas中(而不是DOM里),然后你就可以通过读取canvas的像素点来获得源图片的内容了(这个过程非常简单)。
有关部分的代码如下(2D tunnel effect中用来读取隧道的纹理数据的):
var&loadTexture&=&function&(name,&then)&{&&&&&&var&texture&=&new&Image();&&&&&&var&textureD&&&&&&var&textureW&&&&&&var&textureH&&&&&&var&result&=&{};&&&&&&&&&&&&texture.addEventListener(&load&,&function&()&{&&&&&&&&&&var&textureCanvas&=&document.createElement(&canvas&);&&&&&&&&&&&&&&&&&&&&&textureCanvas.width&=&this.&&&&&&&&&&&textureCanvas.height&=&this.&&&&&&&&&&result.width&=&this.&&&&&&&&&&result.height&=&this.&&&&&&&&&&var&textureContext&=&textureCanvas.getContext(&2d&);&&&&&&&&&&textureContext.drawImage(this,&0,&0);&&&&&&&&&&result.data&=&textureContext.getImageData(0,&0,&this.width,&this.height).&&&&&&&&&&then();&&&&&&},&false);&&&&&&&&&&&&texture.src&=&&&&&&&return&&&};&
为了使用这些代码,你还要保证隧道纹理图片的导入是异步的,因此你需要传递then参数,代码如下:
&&var&texture&=&loadTexture(&soft.png&,&function&()&{&&&&&&&&&&&&QueueNewFrame();&&});&
使用硬件缩放功能
现代浏览器和Windows8都支持硬件加速的canvas,这意味着,你可以使用GPU来调整canvas里面内容的尺寸。
在2D tunnel effect里,该算法要求处理canvas的每一个像素点,因此一个的canvas就得处理786432个像素点,并且为了达到流畅性的要求,每分钟得处理60次,也就是说每分钟要处理个像素点!
很显然,任何可以减少像素处理总数的方法都能带来极大的性能提升。
又一次轮到canvas上场了!下面的代码展示了怎样使用硬件加速来调整canvas的内部有效区域使之等于DOM对象的外部尺寸:
&&canvas.width&=&300;&&canvas.style.width&=&window.innerWidth&+&&px&;&&canvas.height&=&200;&&canvas.style.height&=&window.innerHeight&+&&px&;&
请注意DOM对象的尺寸(canvas.style.width、canvas.style.height)和canvas有效区域的尺寸(canvas.width、canvas.height)之间的差别。
当这两个尺寸不同的时候,硬件会自动调整有效区域的大小,这是一件很棒的事:我们可以绘制低分辨率的图形,然后通过GPU的调整使之符合DOM对象的大小(实现一个漂亮免费的模糊滤镜效果)。
在这种情况下,本来只有300&200 的图像会被GPU扩展到跟你的窗口一样大。
所有的现代浏览器都支持该功能,因此你可以放心的使用。
优化rendering loop
制作游戏的时候,需要一个rendering loop用来绘制所有的组件(如背景,界面,分数等等)。这个loop是代码的核心,因此必须充分优化从而保证游戏的快速和流畅。
RequestAnimationFrame
HTML5一个有趣的功能是使用window.requestAnimationFrame. 代替window.setInterval&来创建定时器,从而实现每(1000/16) 毫秒渲染一次(以达到60fps),你可以通过requestAnimationFrame将该任务交给浏览器。调用这个方法表明你想要尽快的更新有关的图形。
浏览器会将你的请求放入内部渲染计划,并使之与其本身的渲染及动画代码(CSS, transitions等等)同步。这个方法另一个有趣的地方在于如果窗口不显示(minimized, fully occluded等等),你的代码就不会被调用。
这能改善性能,因为浏览器可以优化并发渲染从而提高动画的流畅性(如你的渲染周期太长的话浏览器会将它与其本身的渲染及动画周期同步)。
代码很清晰(别忘了window前缀):
var&intervalID&=&-1;&&var&QueueNewFrame&=&function&()&{&&&&&&if&(window.requestAnimationFrame)&&&&&&&&&&window.requestAnimationFrame(renderingLoop);&&&&&&else&if&(window.msRequestAnimationFrame)&&&&&&&&&&window.msRequestAnimationFrame(renderingLoop);&&&&&&else&if&(window.webkitRequestAnimationFrame)&&&&&&&&&&window.webkitRequestAnimationFrame(renderingLoop);&&&&&&else&if&(window.mozRequestAnimationFrame)&&&&&&&&&&window.mozRequestAnimationFrame(renderingLoop);&&&&&&else&if&(window.oRequestAnimationFrame)&&&&&&&&&&window.oRequestAnimationFrame(renderingLoop);&&&&&&else&{&&&&&&&&&&QueueNewFrame&=&function&()&{&&&&&&&&&&};&&&&&&&&&&intervalID&=&window.setInterval(renderingLoop,&16.7);&&&&&&}&&};&
你只需要在rendering loop的结尾调用这个函数并在接下来的代码段里进行注册即可:
var&renderingLoop&=&function&()&{&&&&&&&&&&&&&QueueNewFrame();&&};&
访问DOM(Document Object Model)
为了优化rendering loop,你必须遵循这条黄金准则:DO NOT ACCESS THE DOM(不要访问DOM)。即使现代浏览器在这方面做了优化,读取DOM对象属性还是太慢了。
例如,在我的代码里,我使用了Internet Explorer 10 profiler(IE10的提供的分析器,按F12快捷键打开),显示的结果如下:
如图所示,访问canvas的宽度和高度会花费大量的时间!
原始代码如下:
var&renderingLoop&=&function&()&{&&&&&&for&(var&y&=&-canvas.height&/&2;&y&&&canvas.height&/&2;&y++)&{&&&&&&&&&&for&(var&x&=&-canvas.width&/&2;&x&&&canvas.width&/&2;&x++)&{&&&&&&&&&&&&&&&&&&&&&&&&&}&&&&&&}&&};&
你可以通过两个变量来预先获取canvas.width 和 canvas.height,然后在后面用变量来代替这些属性值:
var&renderingLoop&=&function&()&{&&&&&&var&index&=&0;&&&&&&for&(var&y&=&-canvasHeight&/&2;&y&&&canvasHeight&/&2;&y++)&{&&&&&&&&&&for&(var&x&=&-canvasWidth&/&2;&x&&&canvasWidth&/&2;&x++)&{&&&&&&&&&&&&&&&&&&&&&&&&&}&&&&&&}&&};&
是不是非常简单?虽然有时候很难注意到这些细节,但是请相信我这绝对是件值得的事。
通过分析器得知,Math.atan2函数比较慢。事实上,该操作并不需要在运行时计算,你可以在JavaScript里面加一些代码预先计算出结果。
一般来说,预先计算一些较为费时的代码是一种好方法。这里,在运行rendering loop之前,我已经计算好了Math.atan2:
&&var&atans&=&[];&&var&index&=&0;&&for&(var&y&=&-canvasHeight&/&2;&y&&&canvasHeight&/&2;&y++)&{&&&&&&for&(var&x&=&-canvasWidth&/&2;&x&&&canvasWidth&/&2;&x++)&{&&&&&&&&&&atans[index++]&=&Math.atan2(y,&x)&/&Math.PI;&&&&&&}&&}&
atans数组的使用明显的提高了性能。
避免使用Math.round,&Math.floor&以及&parseInt
最后一点是parseInt的使用:
当你使用canvas时,你需要使用一些整数坐标。实际上,所有的计算都采用的浮点数,你需要将它们转换成整形。
JavaScript 提供了&Math.round, Math.floor&甚至&parseInt&来转换数值。但是这个方法做了一些额外的工作(比如检测数据是不是有效的数值,parseInt&甚至先将参数转换成了字符串!)。在我的rendering loop里面,我需要一个更快的转换方法。
在我的旧的汇编代码里面,我使用了一个小技巧:将数据右移0位。这会将浮点数从浮点寄存器移到整数寄存器,并且是通过硬件转换的。右移0位不会改变数据的值,但是会以整数形式返回。
原始代码如下:
u =&parseInt((u & 0) ? texture.width + (u % texture.width) : (u &= texture.width) ? u % texture.width : u);
以下是改进后的代码:
u = ((u & 0) ? texture.width + (u % texture.width) : (u &= texture.width) ? u % texture.width : u)&& 0;
当然该方法要求你的数据是合法的数值。
实现了上述优化之后得到的结果如下:
你可以看到做了这些基本的功能优化之后的表现。
原始的隧道渲染(没做任何优化):
做完上述优化之后:
下表展示了每项优化对帧速率的影响(在我的机器上):
记住这些关键技巧,你就可以为现代浏览器或Windows8制作实时、快速、流畅的游戏了。
原文链接:【编辑推荐】【责任编辑: TEL:(010)】
大家都在看猜你喜欢
热点热点头条头条热点
24H热文一周话题本月最赞
讲师:132695人学习过
讲师:160181人学习过
讲师:91485人学习过
精选博文论坛热帖下载排行
本书全面深入地介绍网络安全的配置与实现技术,包括系统管理、用户账户、病毒防御、灾难恢复、文件备份、安全策略、注册表等服务器安全,用...
订阅51CTO邮刊博客分类:
我们继续这一系列文章,使用HTML5的canvas组件进行游戏开发。今天,这是相当完整的游戏例子 – 它会回顾经典的旧电脑游戏 – 坦克大战。我会教你使用阵列地图并教你如何检测活动对象(坦克)与环境(基于阵列的地图)的碰撞。
你可以点击这里阅读这一系列教程的前一篇文章:。我们的将基于之前的程序和代码进行开发。
这里有我们的演示和下载包:
好吧,下载所需文件,让我们开始编码!
步骤1: HTML 这里是我演示的HTML,非常简单,对不对?
&!DOCTYPE html&
&html lang="en" &
&meta charset="utf-8" /&
&title&html5游戏制作入门系列教程(五)&/title&
&link href="css/main.css" rel="stylesheet" type="text/css" /&
&script src="js/jquery-1.5.2.min.js"&&/script&
&script src="js/script.js"&&/script&
&h2&html5游戏制作入门系列教程(五)&/h2&
&a href="http://html5gamedev.org/?p=330" class="stuts"&返回原文&span&HTML5GAME&/span&&/a&
&div class="container"&
&canvas id="scene" width="800" height="600"&&/canvas&
步骤2:CSS
下面是使用CSS样式。 css/main.css 今天就不把css样式贴出来了,和以前的一样,没有什么特别之处。你可以在下载包里找到它。
步骤3:JS js/script.js
// inner variables
var canvas, // canvas and context objects
var imgBrick, imgSteel, imgWater, imgForest, imgT // images
var aM // map array
var oT // tank object
var iCellSize = 24; // cell wide
var iXCnt = 26; // amount of X cells
var iYCnt = 26; // amount of Y cells
// objects :
function Tank(x, y, w, h, image) {
this.i = 0;
this.image =
// functions
function clear() { // clear canvas function
context.clearRect(0, 0, canvas.width, canvas.height);
function drawScene() { // main drawScene function
clear(); // clear canvas
// fill background
context.fillStyle = '#111';
context.fillRect(0, 0, canvas.width, canvas.height);
// save current context
context.save();
// walk through our array
for (var y = 0; y & iYC y++) {
for (var x = 0; x & iXC x++) {
switch (aMap[y][x]) {
case 0: // skip
case 1: // draw brick block
context.drawImage(imgBrick, 0, 0, iCellSize, iCellSize, x*iCellSize, y*iCellSize, iCellSize, iCellSize);
case 2: // draw steel block
context.drawImage(imgSteel, 0, 0, iCellSize, iCellSize, x*iCellSize, y*iCellSize, iCellSize, iCellSize);
case 3: // draw forest block
context.drawImage(imgForest, 0, 0, iCellSize, iCellSize, x*iCellSize, y*iCellSize, iCellSize, iCellSize);
case 4: // draw water block
context.drawImage(imgWater, 0, 0, iCellSize, iCellSize, x*iCellSize, y*iCellSize, iCellSize, iCellSize);
// restore current context
context.restore();
// draw tank
context.drawImage(oTank.image, oTank.i*oTank.w, 0, oTank.w, oTank.h, oTank.x, oTank.y, oTank.w, oTank.h);
// -------------------------------------------------------------
// initialization
$(function(){
canvas = document.getElementById('scene');
canvas.width = iXCnt * iCellS
canvas.height = iYCnt * iCellS
context = canvas.getContext('2d');
// main scene Map array
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
[0, 0, 1, 1, 4, 4, 4, 4, 0, 0, 2, 2, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 1, 4, 4, 4, 4, 0, 0, 2, 2, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 4, 4, 4, 4, 1, 1, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 0, 0, 2, 2, 0, 0],
[0, 0, 0, 0, 4, 4, 4, 4, 1, 1, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 0, 0, 2, 2, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 1, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 1, 1, 0, 0, 0, 0],
[0, 0, 2, 2, 0, 0, 0, 0, 4, 4, 4, 4, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 2, 2, 0, 0, 0, 0, 4, 4, 4, 4, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[3, 3, 3, 3, 1, 1, 0, 0, 4, 4, 4, 4, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0],
[3, 3, 3, 3, 1, 1, 0, 0, 4, 4, 4, 4, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0],
[3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 2, 2],
[3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 2, 2],
[0, 0, 1, 1, 4, 4, 4, 4, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 1, 4, 4, 4, 4, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[2, 2, 0, 0, 4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3, 0, 0, 1, 1, 0, 0],
[2, 2, 0, 0, 4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3, 0, 0, 1, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 0, 0],
[0, 0, 0, 0, 0, 0, 2, 2, 3, 3, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 4, 4, 4, 4, 0, 0],
[0, 0, 0, 0, 0, 0, 2, 2, 3, 3, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 4, 4, 4, 4, 0, 0],
[0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 2, 2, 0, 0, 0, 0],
[1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 2, 2, 0, 0, 0, 0]
// load images
imgBrick = new Image();
imgBrick.src = 'images/brick.png';
imgSteel = new Image();
imgSteel.src = 'images/steel.png';
imgWater = new Image();
imgWater.src = 'images/water.png';
imgForest = new Image();
imgForest.src = 'images/forest.png';
imgTank = new Image();
imgTank.src = 'images/tank.png';
oTank = new Tank(iCellSize*9, iCellSize*24, 48, 48, imgTank);
$(window).keydown(function(event){ // keyboard alerts
switch (event.keyCode) {
case 38: // Up key
oTank.i = 2;
// checking collisions
var iCurCelX = (2 * oTank.x) / 48;
var iCurCelY = (2 * oTank.y) / 48;
if (iCurCelY) {
var iTest1 = aMap[iCurCelY-1][iCurCelX];
var iTest2 = aMap[iCurCelY-1][iCurCelX+1];
if ((iTest1 == 0 || iTest1 == 3) && (iTest2 == 0 || iTest2 == 3)) {
oTank.y-=24;
if (oTank.y & 0) {
oTank.y = 0;
case 40: // Down key
oTank.i = 3;
// checking collisions
var iCurCelX = (2 * oTank.x) / 48;
var iCurCelY = (2 * oTank.y) / 48;
if (iCurCelY+2 & iYCnt) {
var iTest1 = aMap[iCurCelY+2][iCurCelX];
var iTest2 = aMap[iCurCelY+2][iCurCelX+1];
if ((iTest1 == 0 || iTest1 == 3) && (iTest2 == 0 || iTest2 == 3)) {
oTank.y+=24;
if (oTank.y & 576) { //iCellSize * (iYCnt-2)
oTank.y = 576;
case 37: // Left key
oTank.i = 1;
// checking collisions
var iCurCelX = (2 * oTank.x) / 48;
var iCurCelY = (2 * oTank.y) / 48;
var iTest1 = aMap[iCurCelY][iCurCelX-1];
var iTest2 = aMap[iCurCelY+1][iCurCelX-1];
if ((iTest1 == 0 || iTest1 == 3) && (iTest2 == 0 || iTest2 == 3)) {
oTank.x-=24;
if (oTank.x & 0) {
oTank.x = 0;
case 39: // Right key
oTank.i = 0;
// checking collisions
var iCurCelX = (2 * oTank.x) / 48;
var iCurCelY = (2 * oTank.y) / 48;
var iTest1 = aMap[iCurCelY][iCurCelX+2];
var iTest2 = aMap[iCurCelY+1][iCurCelX+2];
if ((iTest1 == 0 || iTest1 == 3) && (iTest2 == 0 || iTest2 == 3)) {
oTank.x+=24;
if (oTank.x & 576) { //iCellSize * (iXCnt-2)
oTank.x = 576;
setInterval(drawScene, 40); // loop drawScene
结论 超级酷,不是吗?我会很高兴看到您的评论和意见。祝你好运!
转载请注明: >>
下载次数: 16
来自: 洛杉矶
试试 pageoffice 在线打开 PDF 文件吧. pag ...
opacity: 0.5; 个人喜欢这种方式!关于其他css特 ...
推荐用StratoIO打印控件,浏览器和系统的兼容性都很好,而 ...
同样有点困惑,试着猜一下原因:【已授权的临时凭证】,一般是UR ...
用javascrip在浏览器上实现语音输入和语义理解功能(sp ...
(window.slotbydup=window.slotbydup || []).push({
id: '4773203',
container: s,
size: '200,200',
display: 'inlay-fix'鍗氬?鍒嗙被锛