HTML5 Canvas小游戏基础:用户交互

      交互是游戏的根本。缺少了用户交互,游戏就不能称之为游戏,只能说是动画或电影。事件是浏览器响应用户交互操作的一种机制。

1.事件和事件执行

      事件定义了用户与页面交互时产生的各种操作(主要通过鼠标或热键的动作引发),例如单击页面中某一个对象时,就产生一个单击(onClick)操作事件。浏览器在程序运行的大部分时间都等待交互事件的发生,并在事件发生时,自动调用事件处理函数,完成事件处理过程。

      事件不仅可以在用户交互过程中产生,而且浏览器自己的一些动作也可以产生事件,例如,当载入一个页面时,就会发生onLoad事件,卸载一个页面时,就会发生onUnload事件等。

      常见的HTML事件有:

onchange        HTML元素改变

onclick         用户点击 HTML 元素

onmouseover  用户在一个HTML元素上移动鼠标

onmouseout    用户从一个HTML元素上移开鼠标

onkeydown     用户按下键盘按键

onload          浏览器已完成页面的加载

      当在HTML页面中使用JavaScript时,JavaScript可以触发这些事件。这样,可以在事件触发时通过JavaScript执行一些代码完成特定的操作。

       例如,通过单击一个“改变”按钮激活change()事件处理程序的方法为:

         <button onclick=’change ()’>改变</button>

      在onClick等号后,可以使用自己编写的函数作为事件处理程序,也可以使用JavaScript中内部的函数,还可以直接使用JavaScript的代码等。例如:

        <button onclick= alert(“这是一个确定按钮”>确定</button>

       在HTML页面中,可以使用下列几种方法来执行JavaScript事件代码。

       (1)HTML事件属性可以直接执行JavaScript代码或调用 JavaScript 函数。

       例1  水平运动的小木块。

       在HTML页面中有一块画布和两个按钮,单击“开始”按钮后,在画布中有一个红色小木块进行水平移动,单击“暂停”按钮后,小木块停止移动,再次单击“开始”按钮,小木块继续移动。

       编写两个事件处理函数start()和stop(),并为两个按钮的onClick事件属性指定各自调用的函数。

编写如下的HTML代码。

<!DOCTYPE html>

<head>

<title>按钮控制的简单动画</title>

</head>

<body>

<canvas id=”myCanvas” width=”300″ height=”200″ style=”border:3px double #996633;”>

</canvas><br>

<button onClick=’start()’>开始</button> &nbsp;&nbsp;

<button onClick=’stop()’>暂停</button><br>

<script type=”text/javascript”>

   var canvas = document.getElementById(‘myCanvas’); 

   var ctx = canvas.getContext(‘2d’);

   var handle = 0;

   var i=0;

   var running=false;

   function move()

   {       

      ctx.clearRect(0,0,canvas.width,canvas.height);

      ctx.fillStyle = “red”;

      ctx.fillRect(i,90, 20, 20);

      i=i+3;

      if (i>=canvas.width) i=0;

      handle = window.requestAnimationFrame(move); 

   }

   function start()

   {

      if (!running)

      {

         handle=requestAnimationFrame(move);

         running=true;

      }    

   }

   function stop()

   {

       window.cancelAnimationFrame(handle);

       handle=null;

       running=false;

   }

</script>

</body>

</html>

       在浏览器中打开包含这段HTML代码的html文件,可以看到在浏览器窗口中看到受按钮控制的水平移动的小木块,如图1所示。

 

图1  水平运动的小木块

      (2)为HTML元素指定相应的事件处理程序。

       可以在JavaScript代码中为为HTML元素指定相应的事件处理程序。例如,例1的程序也可以编写如下。

<!DOCTYPE html>

<head>

<title>按钮控制的简单动画</title>

</head>

<body>

<canvas id=”myCanvas” width=”300″ height=”200″ style=”border:3px double #996633;”>

</canvas><br>

<button id=”start”>开始</button> &nbsp;&nbsp;

<button id=”stop”>暂停</button><br>

<script type=”text/javascript”>

   var canvas = document.getElementById(‘myCanvas’); 

   var ctx = canvas.getContext(‘2d’);

   var handle = 0;

   var i=0;

   var running=false;

   function move()

   {        

      ctx.clearRect(0,0,canvas.width,canvas.height);

      ctx.fillStyle = “red”;

      ctx.fillRect(i,90, 20, 20);

      i=i+3;

      if (i>=canvas.width) i=0;

      handle = window.requestAnimationFrame(move); 

   }

   var startBtn=document.getElementById(‘start’);

   var stopBtn=document.getElementById(‘stop’);

   startBtn.onclick=function()

   {

      if (!running)

      {

         handle=requestAnimationFrame(move);

         running=true;

      }    

   }

   stopBtn.onclick=function()

   {

       window.cancelAnimationFrame(handle);

       handle=null;

       running=false;

   }

</script>

</body>

</html>

      (3)使用addEventListener()方法。

       通过addEventListener()方法可以为指定的HTML元素添加事件句柄,调用格式为:

         element.addEventListener(event, function, useCapture)

      其中:参数event必选。它是一个字符串,用于指定事件名。需要注意的是,指定事件名不要使用“on”前缀。 例如,使用”click”,而不是使用”onclick”。

      参数function  也是必选。用于指定要事件触发时执行的函数。

      参数useCapture可选。它是一个布尔值,指定事件是否在捕获或冒泡阶段执行。参数值为true时,事件句柄在捕获阶段执行;为false(默认值)时,事件句柄在冒泡阶段执行。

      例如,语句  

      canvas.addEventListener(‘mousedown’, function(){

           alert(“Mouse pressed on canvas.”);

      }, false);

      就为canvas绑定了鼠标点击事件,当在canvas上按下鼠标时,就会弹出一个对话框,该对话框中显示信息“Mouse pressed on canvas.”。

      例1中的程序若采用addEventListener()方法为两个按钮添加事件句柄,可以改写如下。

<!DOCTYPE html>

<head>

<title>按钮控制的简单动画</title>

</head>

<body>

<canvas id=”myCanvas” width=”300″ height=”200″ style=”border:3px double #996633;”>

</canvas><br>

<button id=”start”>开始</button> &nbsp;&nbsp;

<button id=”stop”>暂停</button><br>

<script type=”text/javascript”>

   var canvas = document.getElementById(‘myCanvas’); 

   var ctx = canvas.getContext(‘2d’);

   var handle = 0;

   var i=0;

   var running=false;

   function move()

   {       

      ctx.clearRect(0,0,canvas.width,canvas.height);

      ctx.fillStyle = “red”;

      ctx.fillRect(i,90, 20, 20);

      i=i+3;

      if (i>=canvas.width) i=0;

      handle = window.requestAnimationFrame(move); 

   }

   function start()

   {

      if (!running)

      {

         handle=requestAnimationFrame(move);

         running=true;

      }    

   }

   function stop()

   {

       window.cancelAnimationFrame(handle);

       handle=null;

       running=false;

   }

   var startBtn=document.getElementById(‘start’);

   var stopBtn=document.getElementById(‘stop’);

   startBtn.addEventListener(‘click’, start);

   stopBtn.addEventListener(‘click’, stop);

</script>

</body>

</html>

      有时候,可以直接把事件处理函数写在addEventListener()方法中。例如,上面的代码也可以编写如下。

<!DOCTYPE html>

<head>

<title>按钮控制的简单动画</title>

</head>

<body>

<canvas id=”myCanvas” width=”300″ height=”200″ style=”border:3px double #996633;”>

</canvas><br>

<button id=”start”>开始</button> &nbsp;&nbsp;

<button id=”stop”>暂停</button><br>

<script type=”text/javascript”>

   var canvas = document.getElementById(‘myCanvas’); 

   var ctx = canvas.getContext(‘2d’);

   var handle = 0;

   var i=0;

   var running=false;

   function move()

   {       

      ctx.clearRect(0,0,canvas.width,canvas.height);

      ctx.fillStyle = “red”;

      ctx.fillRect(i,90, 20, 20);

      i=i+3;

      if (i>=canvas.width) i=0;

      handle = window.requestAnimationFrame(move); 

   }

   var startBtn=document.getElementById(‘start’);

   var stopBtn=document.getElementById(‘stop’);

   startBtn.addEventListener(‘click’, function () {

         if (!running)

         {

           handle=requestAnimationFrame(move);

           running=true;

         }

      });

   stopBtn.addEventListener(‘click’, function () {

         cancelAnimationFrame(handle);

         handle=null;

         running=false;

   });

</script>

</body>

</html>

       我们可以通过addEventListener()方法在HTML文档中添加许多事件,添加的事件不会覆盖已存在的事件。

       例如,为“开始”按钮添加两个单击事件:

   startBtn.addEventListener(‘click’, function () {

         if (!running)

         {

           handle=requestAnimationFrame(move);

           running=true;

         }

      });

  startBtn.addEventListener(‘click’, function(){

        alert(“Mouse pressed on canvas.”);

    }, false);

      执行时,这两个事件处理代码都会被执行到。

      也可以为同一个元素中添加不同类型的事件。

例如,为canvas画布添加多个事件:

  canvas.addEventListener(‘click’, function(){

        alert(“Mouse click on canvas.”);

    }, false);

  canvas.addEventListener(‘mouseover’, function(){

        alert(“Mouse mouseover to canvas.”);

    }, false);

  canvas.addEventListener(‘mouseout’, function(){

        alert(“Mouse mouseout from canvas.”);

    }, false);

2.鼠标事件

       鼠标事件是由鼠标或类似用户动作触发的事件。鼠标事件属性及触发时机描述如下:

onclick            元素上发生鼠标点击时触发。

ondblclick       元素上发生鼠标双击时触发。

ondrag            元素被拖动时运行的脚本。

ondragend             在拖动操作末端运行的脚本。

ondragenter           当元素元素已被拖动到有效拖放区域时运行的脚本。

ondragleave           当元素离开有效拖放目标时运行的脚本。

ondragover            当元素在有效拖放目标上正在被拖动时运行的脚本。

ondragstart         在拖动操作开端运行的脚本。

ondrop               当被拖元素正在被拖放时运行的脚本。

onmousedown        当元素上按下鼠标按钮时触发。

onmousemove        当鼠标指针移动到元素上时触发。

onmouseout           当鼠标指针移出元素时触发。

onmouseover         当鼠标指针移动到元素上时触发。

onmouseup            当在元素上释放鼠标按钮时触发。

onmousewheel       当鼠标滚轮正在被滚动时运行的脚本。

onscroll              当元素滚动条被滚动时运行的脚本。

       每一个鼠标事件e都包含两个属性来决定当前鼠标的位置:pageX和pageY。通过pageX和pageY,还有canvas元素的偏移位置,我们就能够计算出鼠标具体是在canvas元素的什么位置。

       例2  湖面的小水滴。

       用鼠标点击画布,在鼠标点击的位置一个小圆慢慢变大向外扩展,颜色慢慢变淡,像在湖面上仍一个小石子,一个小水滴慢慢扩散。

编写如下的HTML代码。

<!DOCTYPE html>

<html>

<head>

<title>湖面的小水滴</title>

<script type=”text/javascript”>

    var ctx;

    var cw;

    var ch;

    var waterDrops = [];

    var maxRadius;

    function draw()

    {

           canvas = document.getElementById(‘myCanvas’);

           ctx = canvas.getContext(‘2d’);      

           cw = canvas.width;

           ch = canvas.height;

        maxRadius=Math.sqrt(cw*cw+ch*ch)/2;     

           setInterval(“drawEverything()”,30);

           canvas.addEventListener(‘mousedown’,createWaterDrop);  

    }

    function createWaterDrop(evt)

    {

        waterDrops.push(new WaterDrop(evt.pageX,evt.pageY)); 

    }

    function drawEverything()

    {

           ctx.fillStyle = ‘black’;

           ctx.fillRect(0,0, cw,ch);

        var i=waterDrops.length;

        while (i–)

        {

            waterDrops[i].draw();

            waterDrops[i].update();

            if (waterDrops[i].radius > maxRadius)

            {

                waterDrops.splice(i, 1);

            }

        }

    }

    function WaterDrop(posX, poxY)

    {

           this.radius = 0;

           this.x = posX;

           this.y = poxY;

    }

    WaterDrop.prototype.draw= function()

    {

           ctx.strokeStyle = “rgb(“+ (255 – this.radius) + “,” + (255-this.radius) +”,”+ (255-this.radius) +”)”;

           ctx.beginPath();

           ctx.arc(this.x, this.y, this.radius, 0, 2*Math.PI, true);

           ctx.stroke();          

    }

    WaterDrop.prototype.update= function()

    {

        this.radius+=2;

    }

</script>

</head>

<body onload=”draw();”>

<canvas id=”myCanvas” width=”400″ height=”300″ style=”border:3px double #996633;”>

</canvas>

</body>

</html>

      在浏览器中打开包含这段HTML代码的html文件,可以看到在浏览器窗口中如图2所示的向外扩散的小水滴效果。

 

图2  向外扩散的小水滴

      例3  鼠标单击选择圆球。

      在画布中绘制10个随机大小的填充色为蓝色的圆球,用鼠标单击某个圆球,被单击的圆球的填充色变成红色。

       本例的关键在于判断鼠标点击当前位置是否在所绘制的圆球的路径内,可以采用isPointInPath()来完成。其调用格式为:

         ctx.isPointInPath(x,y);

      如果指定的点(x,y)位于当前路径中,isPointInPath() 方法返回 true;否则返回 false。

      isPointPath方法检测的是当前的路径,这样在点击的过程中需要重新画圆的路径(遍历圆球的数组画圆),每画一个圆就检测鼠标点击的点是否在当前的圆的路径中。

编写HTML代码如下。

<!DOCTYPE html>

<html>

<head>

<title>鼠标单击选择圆球</title>

</head>

<body>

<canvas id=”myCanvas” width=”500″ height=”400″ style=”border:3px double #996633;”>

</canvas>

<script type=”text/javascript”>

    var canvas = document.getElementById(‘myCanvas’);

    ctx = canvas.getContext(‘2d’);

    var balls = [];

    for (var i = 0; i<10; i++)

    {

        var ball = {

            X: random(30,canvas.width-30),

            Y: random(30,canvas.height-30),

            R: random(10,30)

        }

        balls[i] = ball;

    }

    ctx.fillStyle = “blue”;

    for (var i = 0; i < balls.length; i++)

    {

        ctx.beginPath();

        ctx.arc(balls[i].X, balls[i].Y, balls[i].R, 0, Math.PI*2);

        ctx.fill();

    }  

    function random(min,max)

    {

        return Math.floor(Math.random()*(max-min)+min)

    }

    canvas.addEventListener(‘click’, function(){

        var x = event.pageX – canvas.getBoundingClientRect().left;

        var y = event.pageY – canvas.getBoundingClientRect().top;

        for (var i = 0; i < balls.length; i++)

        {

            ctx.beginPath();

            ctx.arc(balls[i].X, balls[i].Y, balls[i].R, 0, Math.PI*2);

            if (ctx.isPointInPath(x, y))

            {

                ctx.fillStyle = “red”;

                ctx.fill();

            }

        }

    }); 

</script>

</body>

</html>

       在浏览器中打开包含这段HTML代码的html文件,可以看到在浏览器窗口中看到如图3所示的交互效果。

 

图3  鼠标单击圆球变色

      例4  通过鼠标拖曳移动圆球。

      在画布中绘制7个随机大小的圆球,用鼠标点击某个圆球,然后按住鼠标左键移动鼠标,被选中的圆球跟随鼠标进行移动。

<!DOCTYPE html>

<html>

<head>

<title通过鼠标拖曳圆球</title>

</head>

<body>

<canvas id=”myCanvas” width=”500″ height=”400″ style=”border:3px double #996633;”>

</canvas>

<script type=”text/javascript”>

    var canvas = document.getElementById(‘myCanvas’);

    ctx = canvas.getContext(‘2d’);

    var colors = [“red”, “orange”,”yellow”, “green”, “cyan”, “blue”,”purple”];

    var balls = [];

    for (var i = 0; i<7; i++)

    {

        var ball = {

            X: random(40,canvas.width-40),

            Y: random(40,canvas.height-40),

            R: random(20,40),

            C: colors[i],

            isSelected: false

        }

        balls[i] = ball;

    }

    function drawBalls()

    {

        ctx.clearRect(0, 0, canvas.width, canvas.height);

        for (var i=0; i<balls.length; i++)

        {

            var ball = balls[i];

            ctx.globalAlpha = 0.85;

            ctx.beginPath();

            ctx.arc(ball.X, ball.Y, ball.R, 0, Math.PI*2);

            ctx.fillStyle = ball.C;

            ctx.strokeStyle = “black”;

            if (ball.isSelected)

                ctx.lineWidth = 5;

            else

                ctx.lineWidth = 1;

            ctx.fill();

            ctx.stroke();

         }

    }

    function random(min,max)

    {

        return Math.floor(Math.random()*(max-min)+min)

    }

    var preSele;

    var isDragging = false;

    drawBalls();

    canvas.addEventListener(‘mousedown’, function(){

        var x = event.pageX – canvas.getBoundingClientRect().left;

        var y = event.pageY – canvas.getBoundingClientRect().top;

        // 查找被单击的圆球

        for (var i=balls.length-1; i>=0; i–)

        {

            var ball = balls[i];

            var dist = Math.sqrt(Math.pow(ball.X-x,2)+ Math.pow(ball.Y- y,2));

            if (dist<= ball.R)

            {

                 if (preSele!= null) preSele.isSelected = false;

                 preSele = ball;

                 ball.isSelected = true;

                 isDragging = true;

                 drawBalls();

                 return;

            }

        }

    });

    canvas.addEventListener(‘mouseup’, function(){

        isDragging = false;

    });

    canvas.addEventListener(‘mouseout’, function(){

        isDragging = false;

    });

    canvas.addEventListener(‘mousemove’, function(){

        if (isDragging == true)

        {

            if (preSele!= null)

            {

                var x = event.pageX – canvas.getBoundingClientRect().left;

                var y = event.pageY – canvas.getBoundingClientRect().top;

                preSele.X = x;

                preSele.Y = y;

                drawBalls();

            }

        }

    });

</script>

</body>

</html>

      在浏览器中打开包含这段HTML代码的html文件,可以看到在浏览器窗口中看到如图4所示的交互效果。

 

图4  通过鼠标拖曳移动圆球

       例5  涂鸦。

       编写一个简单的涂鸦程序。鼠标按下开始涂鸦,鼠标移动划线,鼠标松开,结束涂鸦。用到鼠标事件mousedown、mousemove和mouseup。

<!DOCTYPE html>

<html>

<head>

<title>涂鸦</title>

</head>

<body>

<canvas id=”myCanvas” width=”500″ height=”400″ style=”border:3px double #996633;”>

</canvas>

<script type=”text/javascript”>

    var canvas = document.getElementById(‘myCanvas’);

    ctx = canvas.getContext(‘2d’);

    var started = false;

    canvas.addEventListener(‘mousedown’, function(){

        var x = event.pageX – canvas.getBoundingClientRect().left;

        var y = event.pageY – canvas.getBoundingClientRect().top;

        ctx.beginPath();

        ctx.moveTo(x,y);

        started = true;  

    });

    canvas.addEventListener(‘mousemove’, function(){

        var x = event.pageX – canvas.getBoundingClientRect().left;

        var y = event.pageY – canvas.getBoundingClientRect().top;

        if (started)

        {

            ctx.lineTo(x,y);

            ctx.stroke();

        }

    });

    canvas.addEventListener(‘mouseup’, function(){

        started=false;

    });

</script>

</body>

</html>

       在浏览器中打开包含这段HTML代码的html文件,可以看到在浏览器窗口中看到如图5所示的交互效果。

 

图5 涂鸦 

3.键盘事件

键盘事件主要的有三个:

onkeydown     当按下按键时运行脚本

onkeypress     当按下并松开按键时运行脚本

onkeyup       当松开按键时运行脚本

      其中,onkeypress在按键持续按住的过程中会不断地运行脚本,而keydown只会在按键按下时执行一次,keypress在游戏开发中适用于使用键盘操控游戏角色的移动。

      HTML5 Canvas本身不支持键盘事件监听与获取,一般通过windows对象来实现Canvas键盘事件监听与处理,例如:

         window.addEventListener(‘keydown’, doKeyDown,true);

      例6  通过键盘移动矩形小木块。

       键盘事件有许多重要的应用,例如在游戏中通过上下左右键来控制角色的移动。本例就是通过上下左右键来控制一个矩形小木块的移动。

<!DOCTYPE html>

<html>

<head>

<title>通过键盘移动小木块</title>

</head>

<body>

<canvas id=”myCanvas” width=”500″ height=”400″ style=”border:3px double #996633;”>

</canvas>

<script type=”text/javascript”>

    var canvas = document.getElementById(‘myCanvas’);

    ctx = canvas.getContext(‘2d’);

    var x = canvas.width/2;

    var y = canvas.height/2;

    ctx.fillStyle = “blue”;

    ctx.fillRect(x, y, 40, 40);

    window.addEventListener(‘keydown’, function(e){

        var keyID = e.keyCode ? e.keyCode :e.which;

        if (keyID === 37)    // left 

        {

           ctx.clearRect(0,0,canvas.width,canvas.height);

           if (x>=10) x = x – 10;

           else x=0;

           ctx.fillRect(x, y, 40, 40);

           e.preventDefault();

        }

        if (keyID === 38)     // up

        {  

           ctx.clearRect(0,0,canvas.width,canvas.height);

           if (y>=10) y = y – 10;

           else y=0;

           ctx.fillRect(x, y, 40, 40);

           e.preventDefault();

        }

        if (keyID === 39)   // right

        {

           ctx.clearRect(0,0,canvas.width,canvas.height);

           if (x+50<=canvas.width) x = x + 10;

           else  x=canvas.width-40;

           ctx.fillRect(x, y, 40, 40);

           e.preventDefault();

        }

        if (keyID === 40)   // down 

        {

           ctx.clearRect(0,0,canvas.width,canvas.height);

           if (y+50<=canvas.height) y = y + 10;

           else  y=canvas.height-40;

           ctx.fillRect(x, y, 40, 40);

           e.preventDefault();

        }

    },true);

</script>

</body>

</html>

      在浏览器中打开包含这段HTML代码的html文件,可以看到在浏览器窗口中看到如图6所示的交互效果。

 

图6  通过键盘控制小木块的移动