技术头条 - 一个快速在微博传播文章的方式     搜索本站
您现在的位置首页 --> JavaScript --> 使用JavaScript和Canvas开发游戏(二)

使用JavaScript和Canvas开发游戏(二)

浏览:1769次  出处信息

3、通过Canvas元素实现高级图像操作

http://www.brighthub.com/internet/web-development/articles/39509.aspx

这篇文章将带领大家学习使用JavaScript和Canvas元素操作图像了几种不同的方式,这些方式在Canvas元素出现之前是不可能的事儿。

上一篇文章演示了如何利用Canvas实现一个基本的图像动画。那个例子很简单,同样的效果通过修改IMG或DIV等标准HTML元素的一些属性,照样也可以轻易实现。下面我们就来演示一下画布元素的高级应用,展示一下它的真正威力。

首先,还是准备一个HTML页面。


 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
 <html lang="en">
    <head>
       <title>JavaScript Platformer 2</title>
       <script type="text/javascript" src="jsplatformer2.js"></script>
       <style type="text/css">
          body { font-family: Arial,Helvetica,sans-serif;}
       </style>
    </head>
   <body>
      <p>
         <a href="http://www.brighthub.com/internet/web-development/articles/38364.aspx">
            Game Development with Javascript and the canvas element
         </a>
      </p>
      <canvas id="canvas" width="600" height="400">
         <p>Your browser does not support the canvas element.</p>
      </canvas>
      <br />
      <button onclick="currentFunction=alpha;">Change Alpha</button>
      <button onclick="currentFunction=shear;">Shear</button>
      <button onclick="currentFunction=scale;">Scale</button>
      <button onclick="currentFunction=rotate;">Rotate</button>
   </body>
</html>
01 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
02  <html lang="en">
03     <head>
04        <title>JavaScript Platformer 2</title>
05        <script type="text/javascript" src="jsplatformer2.js"></script>
06        <style type="text/css">
07           body { font-family: Arial,Helvetica,sans-serif;}
08        </style>
09     </head>
10    <body>
11       <p>
13             Game Development with Javascript and the canvas element
14          </a>
15       </p>
16       <canvas id="canvas" width="600" height="400">
17          <p>Your browser does not support the canvas element.</p>
18       </canvas>
19       <br />
20       <button onclick="currentFunction=alpha;">Change Alpha</button>
21       <button onclick="currentFunction=shear;">Shear</button>
22       <button onclick="currentFunction=scale;">Scale</button>
23       <button onclick="currentFunction=rotate;">Rotate</button>
24    </body>
25 </html>

与上个一例子的HTML页面相比,唯一的区别就是添加了一些按钮。单击这些按钮,就会设置currentFunction变量(稍后介绍)的值,用以改变在渲染循环中运行的函数。

以下是 jsplatformer2.js 的代码。


// 每秒多少帧
const FPS = 30;
const SECONDSBETWEENFRAMES = 1 / FPS;
const HALFIMAGEDIMENSION = 75;
const HALFCANVASWIDTH = 300;
const HALFCANVASHEIGHT = 200;
var image = new Image();
image.src = "jsplatformer2-smiley.jpg"; //还是第一个例子中的图像
var canvas = null;
var context2D = null;
var currentFunction = null;
var currentTime = 0;
var sineWave = 0;
window.onload = init;
function init()
{
   canvas = document.getElementById('canvas');
   context2D = canvas.getContext('2d');
   setInterval(draw, SECONDSBETWEENFRAMES * 1000);
   currentFunction = scale;
}
function draw()
{
    currentTime += SECONDSBETWEENFRAMES;
    sineWave = (Math.sin(currentTime) + 1) / 2;
    context2D.clearRect(0, 0, canvas.width, canvas.height);
    context2D.save();
    context2D.translate(HALFCANVASWIDTH - HALFIMAGEDIMENSION, HALFCANVASHEIGHT - HALFIMAGEDIMENSION);
    currentFunction();
    context2D.drawImage(image, 0, 0);
    context2D.restore();
}
function alpha()
{
    context2D.globalAlpha = sineWave;
}
function shear()
{
    context2D.transform(1, 0, (sineWave - 0.5), 1, 0, 0);
}
function scale()
{
    context2D.translate(HALFIMAGEDIMENSION * (1 - sineWave), HALFIMAGEDIMENSION * (1 - sineWave));
    context2D.scale(sineWave, sineWave);
}
function rotate()
{
    context2D.translate(HALFIMAGEDIMENSION, HALFIMAGEDIMENSION);
    context2D.rotate(sineWave * Math.PI * 2);
    context2D.translate(-HALFIMAGEDIMENSION, -HALFIMAGEDIMENSION);
}
01 // 每秒多少帧
02 const FPS = 30;
03 const SECONDSBETWEENFRAMES = 1 / FPS;
04 const HALFIMAGEDIMENSION = 75;
05 const HALFCANVASWIDTH = 300;
06 const HALFCANVASHEIGHT = 200;
07 var image = new Image();
08 image.src = "jsplatformer2-smiley.jpg"; //还是第一个例子中的图像
09 var canvas = null;
10 var context2D = null;
11 var currentFunction = null;
12 var currentTime = 0;
13 var sineWave = 0;
14 window.onload = init;
15 function init()
16 {
17    canvas = document.getElementById('canvas');
18    context2D = canvas.getContext('2d');
19    setInterval(draw, SECONDSBETWEENFRAMES * 1000);
20    currentFunction = scale;
21 }
22 function draw()
23 {
24     currentTime += SECONDSBETWEENFRAMES;
25     sineWave = (Math.sin(currentTime) + 1) / 2;
26     context2D.clearRect(0, 0, canvas.width, canvas.height);
27     context2D.save();
28     context2D.translate(HALFCANVASWIDTH - HALFIMAGEDIMENSION, HALFCANVASHEIGHT - HALFIMAGEDIMENSION);
29     currentFunction();
30     context2D.drawImage(image, 0, 0);
31     context2D.restore();
32 }
33 function alpha()
34 {
35     context2D.globalAlpha = sineWave;
36 }
37 function shear()
38 {
39     context2D.transform(1, 0, (sineWave - 0.5), 1, 0, 0);
40 }
41 function scale()
42 {
43     context2D.translate(HALFIMAGEDIMENSION * (1 - sineWave), HALFIMAGEDIMENSION * (1 - sineWave));
44     context2D.scale(sineWave, sineWave);
45 }
46 function rotate()
47 {
48     context2D.translate(HALFIMAGEDIMENSION, HALFIMAGEDIMENSION);
49     context2D.rotate(sineWave * Math.PI * 2);
50     context2D.translate(-HALFIMAGEDIMENSION, -HALFIMAGEDIMENSION);
51 }

跟前面一样,这个JavaScript文件先定义了一些全局变量。

  • FPS:每秒多少帧
  • SECONDSBETWEENFRAMES:两帧之间间隔的秒数(FPS的倒数)
  • HALFIMAGEDIMENSION:要绘制图像的宽度/高度的一半,用于把图像定位到画布的中心点
  • HALFCANVASWIDTH:画布宽度的一半,用于配合HALFIMAGEDIMENSION使用,以便在画布上居中图像
  • HALFCANVASHEIGHT:画布高度的一半,用于配合HALFIMAGEDIMENSION使用,以便在画布上居中图像
  • currentFunction:渲染循环(参见上一篇文章)中运行的函数
  • currentTime:应用已经运行了多少秒
  • sineWave:0到1之间的一个值,用于控制图像的运动
  • image:要在画布上绘制的图像
  • canvas:画布元素的引用
  • context2D:画布元素的2D上下文的引用

然后,跟前面一样,要设置在window的onload事件发生时立即调用init函数(关于init函数的介绍,请参见上一篇文章)。

draw函数

下面来看一看draw函数:


function draw()
{
    currentTime += SECONDSBETWEENFRAMES;
    sineWave = (Math.sin(currentTime) + 1) / 2;
    context2D.clearRect(0, 0, canvas.width, canvas.height);
    context2D.save();
    context2D.translate(HALFCANVASWIDTH - HALFIMAGEDIMENSION, HALFCANVASHEIGHT - HALFIMAGEDIMENSION);
    currentFunction();
    context2D.drawImage(image, 0, 0);
    context2D.restore();
}
01 function draw()
02 {
03     currentTime += SECONDSBETWEENFRAMES;
04     sineWave = (Math.sin(currentTime) + 1) / 2;
05   
06     context2D.clearRect(0, 0, canvas.width, canvas.height);
07   
08     context2D.save();
09     context2D.translate(HALFCANVASWIDTH - HALFIMAGEDIMENSION, HALFCANVASHEIGHT - HALFIMAGEDIMENSION);
10     currentFunction();
11     context2D.drawImage(image, 0, 0);
12     context2D.restore();
13 }

这个例子要演示4种效果:修改alpha值(透明度),以及缩放、旋转和切变图像。为了展示这些效果,需要基于某一范围内的值来应用变化。变量sineWave就用来定义这个范围值的基准。

标准的正弦函数能够在-1到1之间产生非常完美的波形图。首先,我们通过递增currentTime变量来反映动画已经运行了多长时间,然后再利用这个值在正弦曲线上找到一个点。给正弦函数返回的值(从-1到1)先加1再除以2,就可以把它们转换成0到1这个范围内的值。


currentTime += SECONDSBETWEENFRAMES;
sineWave = (Math.sin(currentTime) + 1) / 2;
1 currentTime += SECONDSBETWEENFRAMES;
2 sineWave = (Math.sin(currentTime) + 1) / 2;

然后,调用clearRect方法清空画布,以便为后面的绘图准备一个干净的版面。


context2D.clearRect(0, 0, canvas.width, canvas.height);
1 context2D.clearRect(0, 0, canvas.width, canvas.height);

应用到画布上面的效果是可以累积的,因而就可以利用几个简单的函数来“组合”出效果来。例如,在向屏幕上绘制之前,可能会有一艘飞船需要旋转、变换和缩放。因为所有效果都对画布起作用,所以这些效果会应用到将被绘制在屏幕上的所有对象,而不仅仅是某一幅图像或某一个形状(比如一艘飞船)。

其中,save和restore函数为应用这些累积的效果提供了一种简单的机制,可以将应用了这些效果的图像或图形绘制到画布上,然后“撤销”这些改变。后台的操作是什么呢?save函数把当前的绘制状态推进栈里,而restore函数则把最后一个状态弹出栈。还拿前面提到的飞船为例,需要执行下列操作:

  • 调用save函数(保存当前的绘制状态)
  • 旋转、变换和缩放上下文
  • 绘制飞船
  • 调用restore函数,移除自上一次调用save方法以来所添加的任何效果,也就是撤销之前的变化

在这里,我们就是要组合起来使用这两个方法。首先,在把任何效果应用到画布之前,先保存绘制状态。

context2D.save();
1 context2D.save();

保存了绘制状态之后,就该应用目标效果了。为此,首先调用translate函数,从而将随后要绘制的图像在画布上居中。

context2D.translate(HALFCANVASWIDTH - HALFIMAGEDIMENSION, HALFCANVASHEIGHT - HALFIMAGEDIMENSION);

1 context2D.translate(HALFCANVASWIDTH - HALFIMAGEDIMENSION, HALFCANVASHEIGHT - HALFIMAGEDIMENSION);

接下来,调用由变量currentFunction引用的函数。正是这些被引用的函数,是让图像发生alpha(透明度)变化以及缩放、旋转和切变的关键。这些函数我们稍后再介绍。


currentFunction();
1 currentFunction();

为图像应用完效果之后,就可以把它绘制到画布上面了。所以,接下来就是调用drawImage来绘图。


context2D.drawImage(image, 0, 0);
1 context2D.drawImage(image, 0, 0);

最后,再调用restore函数,把自调用save函数以来应用的所有效果从画布上移除。


context2D.restore();
1 context2D.restore();

alpha函数


function alpha()
{
    context2D.globalAlpha = sineWave;
}
1 function alpha()
2 {
3     context2D.globalAlpha = sineWave;
4 }

通过修改上下文对象的globalAlpha属性,所有后续绘制操作的透明度都会被修改。将globalAlpha设置为0,意味着被绘制的任何对象都将完全透明,而将这个属性设置为1,则意味着任何绘制操作都会保持原有的透明度级别。在此,我们通过修改这个globalAlpha属性,可以实现笑脸的淡入和淡出效果。

shear函数


function shear()
{
    context2D.transform(1, 0, (sineWave - 0.5), 1, 0, 0);
}
1 function shear()
2 {
3     context2D.transform(1, 0, (sineWave - 0.5), 1, 0, 0);
4 }

切变操作是通过transform函数向画布应用一个矩阵来实现的。变换矩阵本身就是一个值得研究的主题,但对我们来说,如果不想理解背后的数学原理,可以在网上找到很多标准的2D变换矩阵(http://en.wikipedia.org/wiki/Transformation_matrix#Examples_in_2D_graphics),直接使用transform函数来应用它们即可。所谓切变,其实就是把图像的顶部或底部推到一边。

scale函数


 function scale()
 {
     context2D.translate(HALFIMAGEDIMENSION * (1 - sineWave), HALFIMAGEDIMENSION * (1 - sineWave));
56
    context2D.scale(sineWave, sineWave);
 }
1 function scale()
2  {
3      context2D.translate(HALFIMAGEDIMENSION * (1 - sineWave), HALFIMAGEDIMENSION * (1 - sineWave));
4 56
5     context2D.scale(sineWave, sineWave);
6  }

顾名思义,scale(缩放)函数修改的是图像的大小。但在此之前,我们还调用了一次transalte函数。这是为了让缩放后的图像在画布上居中。如果你把这行代码注释掉,就会发现图像会从左上角向右下角膨胀。调用translate函数就是为抵消其圆心的位移,让图像始终居中。

rotate函数


function rotate()
{
    context2D.translate(HALFIMAGEDIMENSION, HALFIMAGEDIMENSION);
    context2D.rotate(sineWave * Math.PI * 2);
    context2D.translate(-HALFIMAGEDIMENSION, -HALFIMAGEDIMENSION);
}
1 function rotate()
2 {
3     context2D.translate(HALFIMAGEDIMENSION, HALFIMAGEDIMENSION);
4     context2D.rotate(sineWave * Math.PI * 2);
5     context2D.translate(-HALFIMAGEDIMENSION, -HALFIMAGEDIMENSION);
6 }

与scale函数类似,rotate(旋转)函数的作用也正如其名:旋转图像。与scale函数同样类似的是,这里也额外调用了translate函数以确保图像围绕中心点而不是左上角旋转。建议大家把对translate函数的调用注释掉,自己看一看结果有什么不同。

刚刚我们看到了使用画布元素实现的4种也还算简单的效果,这些效果使用标准的HTML元素几乎是不可能做到的。其中,有的效果可以使用scale和rotate等内置函数来实现,而使用transform函数则可以完成大量的图像操作(切变只是其中之一)。

看看Demo吧。http://webdemos.sourceforge.net/jsplatformer2/jsplatformer2.html

建议继续学习:

  1. 《部落冲突》的设计    (阅读:3532)
  2. Canvas学习教程 : Canvas介绍    (阅读:3162)
  3. 使用canvas绘制时钟    (阅读:2860)
  4. 手机游戏设计初体验    (阅读:3044)
  5. 游戏多服务器架构的一点想法    (阅读:2860)
  6. 使用JavaScript和Canvas开发游戏    (阅读:2699)
  7. HTML5 Canvas(画布)教程    (阅读:2618)
  8. 游戏程序守护进程-Windows版    (阅读:2532)
  9. 游戏动作感设计初探    (阅读:2447)
  10. 使用JavaScript和Canvas开发游戏(一)    (阅读:2296)
QQ技术交流群:445447336,欢迎加入!
扫一扫订阅我的微信号:IT技术博客大学习
© 2009 - 2024 by blogread.cn 微博:@IT技术博客大学习

京ICP备15002552号-1