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

使用 Canvas 和 JavaScript 的 HTML 游戏

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.99/5 (36投票s)

2014年4月27日

CPOL

24分钟阅读

viewsIcon

51718

downloadIcon

1622

本文旨在通过一个简单的游戏开发,向初学者介绍 HTML Canvas。

引言

本文是12篇文章系列中的第七篇,将带我们学习 HTML5 和 CSS3,特别是通过开发名为 **“气球鲍勃”** 和 **“鲍比卡特”** 的简单游戏来学习 Canvas 及其用途。

本文将介绍以下概念,并在我们的游戏中同时使用它们。

 

什么是 Canvas?

Canvas 元素作为 HTML5 的一部分引入,使我们能够动态渲染 2D 图像。绘图通过使用 Javascript 等语言进行脚本编写来创建。一些常见的实现包括创建游戏、绘制图形、制作照片合成、创建动画,甚至进行实时视频处理或渲染。今天我们将通过在网页上创建简单的游戏来学习 Canvas。

创建 Canvas

Canvas 元素是一个依赖于分辨率的位图 Canvas,它基本上是一个矩形,我们可以在其中使用脚本绘制任何我们喜欢或想要的东西。如果你在想这个元素会是什么样子,那么答案是“**什么都没有**”。Canvas 元素本身没有内容或边框。下面是一个创建 Canvas 的示例。

<canvas id="mycanvas" width="100" height="100">Your Browser Doesn’t support Canvas Element</canvas>

通过 ID 属性,页面中的每个 Canvas 都可以像其他 HTML 元素一样通过脚本进行识别,并且页面上的每个 Canvas 都维护自己的状态。Canvas 标签之间的文本只有在浏览器不识别和不支持 Canvas 元素时才会显示。

浏览器支持

大多数现代浏览器都支持 Canvas 元素,下面是该元素从哪个版本开始支持的快速列表。

  • Internet Explorer (9.0+)
  • Safari (3.0+)
  • Firefox (3.0+)
  • Chrome (3.0+)
  • Opera (10.0+)
  • iOS (1.0+)
  • Android (1.0+)

如果您需要在 IE 9 以下版本中实现 Canvas,那么有一个非常好的解决方案叫做 Explore Canvas

就是这样,我们在页面上创建了一个 Canvas

在我们继续之前,让我告诉你我们今天要构建的游戏,因为我们将通过构建这个游戏来尽可能多地了解 Canvas。

这个游戏是一个简单的打砖块游戏。我们将在 Canvas 的顶部区域放置一些气球,并在下方放置一个蹦床。我们需要弹跳我们的鲍勃,尽可能多地打破气球。在游戏中,鲍勃尖尖的头有什么更好的用途呢?就是这样。我们把这个游戏叫做“**气球鲍勃**”:)

让我们看看构建一些图形需要理解的一些概念。

2D 渲染上下文

2D 渲染上下文,或简称 2D 上下文,是我们绘制形状的部分。当我们说在 Canvas 上绘图时,我们实际上是在上下文上绘图,或者在本例中是在 2D 渲染上下文上绘图,当然可以通过 Canvas 元素访问。Canvas 元素本身不提供任何绘图属性或方法。

getContext() 方法返回一个对象,该对象提供用于在 Canvas 上绘图的方法和属性。

这就是关于 2D 上下文的所有知识,但了解它很重要,因为没有上下文我们就无法完成我们的游戏:)

Canvas 坐标和路径

Canvas 坐标

在之前的文章中,我们学习了很多关于 x、y 和 z。很好,因为我们现在可以重用一些这些知识。在纸上绘图时,我们本质上是在 2D 平面或 2D 上下文上绘图。我们将使用的 2D 渲染上下文并没有太大不同,它使用标准的笛卡尔坐标系,下面是一个带有表示一些点的 2D 上下文的快速图表。我们的 Canvas 基本上是 **第四象限**,其中 x 和 y 值都为正。在大多数情况下,X 和 Y 的每个单位都应该是我们渲染屏幕上的一个像素。

让我们迈出游戏创建的第一步,创建一个 Canvas。

        <div style="width:100%;height:100%;text-align:center">
            <canvas id="BalloonBob" ></canvas>
        </div> 

记住一件事,Canvas 必须始终定义尺寸,因为它默认只获得 300px * 150px(宽度 * 高度)的尺寸。使用 CSS 控制尺寸属性只会导致 Canvas 按这些测量值进行缩放。所以让我们给出 800px 的宽度和 600px 的高度。这意味着在绘图时,我们的 x 最大值为 800,y 最大值为 600。

<div style="width:100%;height:100%;text-align:center">
        <canvas id="scene" width="800" height="600" ></canvas>
</div>

路径

简单来说,Canvas 上的路径是一系列点,这些点之间有绘图指令。

路径用于在 Canvas 上创建形状,包括线条。有了这个,你可能已经明白路径非常重要。在任何给定时间,Canvas 上只能有一个路径。

以下是关键的路径函数。

  • beginPath()
  • closePath()
  • moveTo(x, y)
  • lineTo(x, y)
  • rect(x, y, width, height)
  • arc(x, y, radius, starting Angle, ending Angle, anticlockwiseFlag)
  • quadraticCurveTo(ctrlPtx, ctrlPty, x, y)
  • bezierCurveTo(ctrlPt1x, ctrlPt2y, ctrlPt2x, ctrlPt2y, x, y)
  • arcTo(xa, ya, xb, yb, radius)

我们可能不会在游戏中用到所有这些,但让我们快速回顾一下它们是什么。

如果你们中有人以前使用过 LOGO (面向逻辑图形) 并且仍然记得 *TURTLE* 及其移动,那么这将派上用场,因为在 Canvas 上绘图与在 LOGO 语言中绘图没有什么不同。

在开始路径函数之前,我们需要学习一个名为 stroke() 的函数。

stroke() 函数

此函数实际上使用所有 moveTo()、lineTo、rect 和 closePath() 方法绘制我们定义的路径。默认颜色为黑色,可以通过名为 strokeStyle 的上下文属性更改,该属性接受颜色/渐变/图案作为其值。现在我们使用颜色,稍后将介绍渐变和图案。

Syntax: context.strokeStyle=color|gradient|pattern;

Canvas 上的路径以 beginPath() 和 closePath() 函数开始和结束。

beginPath() **函数:**

此函数总是通过清除任何现有路径来开始一个新的路径。我们很快就会看到此函数的含义和用途。

closePath() 函数

通过从当前位置画一条线回到初始位置来关闭路径。例如,要画一个正方形,您可以画三条边并关闭路径,这将反过来画出第四条线。

moveTo(x, y) 函数

这会将我们的位置移动到给定的坐标,而无需绘制线条。

lineTo(x, y) 函数

这会从当前位置绘制一条线到参数中指定的位置。

rect(x, y, width, height) 函数

这会在您给定的 X/Y 坐标处绘制一个具有给定宽度和高度的矩形。与 fillRect() 和 strokeRect() 函数不同,此函数只添加到当前路径,而不会立即在 Canvas 上绘制。在您调用 stroke() 或 fill() 之前,不会绘制任何内容。

以上是最简单的,所以我将尝试在下面的一个例子中涵盖它们。

示例

以下代码首先创建一个名为 myCanvas 的 Canvas,宽度为 250,高度为 250,我们还添加了一个边框,以便我们可以看到 Canvas 的位置。

然后我们添加一个脚本部分,其中我们首先通过 canvas 元素获取我们的上下文,名为 ctx。

  • 步骤1: 开始路径
  • 步骤2: 我们正在绘制一个矩形,x:20, y:20, 高度:100, 宽度:150。
  • 步骤3: 移动到 x:40 和 y:20
  • 步骤4: 从当前位置绘制一条线到 x:40 和 y:120
  • 步骤5: 将我们的描边样式设置为绿色
  • 步骤6: 关闭路径并调用描边方法,这将创建上述路径(矩形和一条线)
  • 步骤7: 我们正在重复与上面类似的步骤来绘制一个矩形,但我们使用 closePath() 方法来绘制最后一条线。
<html>
<body>
<canvas id="HTMLCanvas" width="600" height="600" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas element. Please reopen the page in a supported browser</canvas>
<script>
var c=document.getElementById("HTMLCanvas");
var bobContext=c.getContext("2d");
bobContext.beginPath();
bobContext.rect(20,20,150,100);
bobContext.moveTo(40,20);
bobContext.lineTo(40,120);
bobContext.strokeStyle = "green";
bobContext.closePath();
bobContext.stroke();
bobContext.beginPath();
bobContext.moveTo(120,120);
bobContext.lineTo(120,200);
bobContext.strokeStyle = "RED";
bobContext.lineTo(170,200);
bobContext.closePath();
bobContext.stroke();
</script>
</body>

arc(x, y, radius, startingAngle, endingAngle, anticlockwise) 函数

Arc 函数用于绘制一个给定半径(第三个参数)的圆或半圆(ARC),中心点是 X,Y(第一和第二个参数)。startingAngle 和 endingAngle 是弧线开始和结束的角度。

角度以弧度而不是度数测量。计算弧度的简单公式是将度数除以 (180/Pi)。Pi 大约是 3.14,在 Javascript 中我们可以使用常量 Math.PI。

0 度角在右侧,也就是圆上 3 点钟的位置,最后一个参数 anticlockwise 决定线的绘制方式,如果我们绘制一个完整的圆则无关紧要,但如果我们不绘制完整的圆,那么我们将不得不使用逆时针来在所需位置获得所需的弧线。

下面是一个简单的例子。

我们首先绘制一个完整的圆和一些半圆,其中逆时针值为 true 和 false。

<script>
var c=document.getElementById("HTMLCanvas");
var bobContext=c.getContext("2d");
bobContext.beginPath();
bobContext.strokeStyle = "red";
bobContext.arc(60,75,50,0,2*Math.PI);
bobContext.stroke();
bobContext.closePath();
bobContext.beginPath();
bobContext.strokeStyle = "green";
bobContext.arc(60,125,40,1.2*Math.PI,1.8*Math.PI);
bobContext.stroke();
bobContext.closePath();
bobContext.beginPath();
bobContext.strokeStyle = "green";
bobContext.arc(180,75,50,1.2*Math.PI,1.8*Math.PI,false);
bobContext.stroke();
bobContext.closePath();
bobContext.beginPath();
bobContext.strokeStyle = "green";
bobContext.arc(300,75,50,1.2*Math.PI,1.8*Math.PI,true);
bobContext.stroke();
</script>

quadraticCurveTo(cpx, cpy, x, y) 函数

此函数有助于绘制二次曲线。起始位置将是我们当前所处的位置,结束位置将是给定的后两个坐标,前两个坐标将用作控制点。

控制点定义了二次曲线的曲率。它通过创建两条假想的切线来实现,这两条切线连接到原点和终点。原点就是我们当前所在的位置,我们可以使用 moveto 方法移动到正确的位置。

通过定义更远的控制点来创建更锐利的曲线,通过定义更近的控制点来创建更宽广的曲线。

Syntax: context.quadraticCurveTo(ctrlptx,ctrlpty,x,y);

示例1

这将不会创建曲线,因为我们的控制点在原始路径上

var c=document.getElementById("HTMLCanvas");
var bobCtx=c.getContext("2d");
bobCtx.beginPath(); 
bobCtx.moveTo(5,1);                   // Create a origin point
bobCtx. quadraticCurveTo(5,5,5,9);    // Create a Quadratic Curve Path
bobCtx.stroke();                      // Stroke it on the Canvas

示例2

var c=document.getElementById("HTMLCanvas");
var bobCtx=c.getContext("2d");
bobCtx.beginPath(); 
bobCtx.moveTo(5,1);                  // Create a origin point
bobCtx. quadraticCurveTo(9,6,5,9);   // Create a Quadratic Curve Path
bobCtx.stroke();                        // Stroke it on the Canvas

示例3

var c=document.getElementById("HTMLCanvas");
var bobCtx=c.getContext("2d");
bobCtx.beginPath(); 
bobCtx.moveTo(5,1);                          // Create a origin point
bobCtx. quadraticCurveTo(9,2,5,9);    // Create a Quadratic Curve Path
bobCtx.stroke();                                // Stroke it on the Canvas

bezierCurveTo(ctrlpt1x, ctrlpt1y, ctrlpt2x, ctrlpt2y, x, y) 函数

此函数有助于绘制贝塞尔曲线。原点将是我们当前所在的位置,终点将是指定的第五和第六个坐标。前 4 个坐标用作控制点。

贝塞尔曲线由原点、两个控制点和终点定义。

正如您所看到的,二次曲线和贝塞尔曲线的区别在于使用的控制点数量。前者使用一个控制点,而后者使用两个控制点。这有助于我们创建更高级的曲线。

  • 第一条曲线与可绘制在原点和第一个控制点之间的线相切。
  • 第二条曲线与可绘制在第二个控制点和终点之间的线相切。
Syntax: context.bezierCurveTo(ctrlpt1x,ctrlpt1y,ctrlpt2x,ctrlpt2y,x,y);

示例

示例1:无变化,因为控制点在路径上

var c=document.getElementById("HTMLCanvas");
var bobContext=c.getContext("2d");
bobContext.beginPath(); 
bobContext.moveTo(5,1);           // Create a origin point
bobContext bezierCurveTo(5,5,7.5,5,5,1); // Create a Path
bobContext.stroke();                // Draw it
bobContext bezierCurveTo(5,5,7.5,5,5,1); //No change as the Control points are on the path

示例2

var c=document.getElementById(HTMLCanvas");
var bobContext=c.getContext("2d");
bobContext.beginPath();
bobContext.moveTo(5,1);           // Create a origin point
bobContext.bezierCurveTo(9,6,9,8,5,1);  // Create a Path
bobContext.stroke();                // Draw it

示例3

var c=document.getElementById("HTMLCanvas");
var bobContext=c.getContext("2d");
bobContext.beginPath(); 
bobContext.moveTo(5,1);           // Create a origin point
bobContext bezierCurveTo(9,2,1,8,5,1);  // Create a Path
bobContext.stroke();                // Draw it

下面是显示上述所有三个示例结果的 GIF 图像。

arcTo(x1, y1, x2, y2, radius) 函数

此方法大多是 ARC 和二次曲线功能的组合。与 arc() 方法一样,它使用当前位置作为起点,以给定半径绘制相似的弧线到 [xb, yb],并使用 [xa, ya] 作为控制点。

Synatax: context.arcTo(xa,ya,xb,yb,r);

示例

var c=document.getElementById("HTMLCanvas");
var bobContext=c.getContext("2d");
bobContext.beginPath(); 
bobContext.moveTo(20,20);           // Create a starting point
bobContext.lineTo(100,20);          // Create a horizontal line
bobContext.arcTo(150,20,150,70,50); // Create an arc
bobContext.lineTo(150,120);         // Continue with vertical line
bobContext.stroke();                // Draw it

好的..!现在我们已经理解了 Canvas 中的路径,让我们开始创建游戏所需的形状。

我们已经在上面创建了一个矩形(800/600)Canvas,现在是时候开始使用脚本在我们的 Canvas 上绘图了。

为鲍勃跳跃创建一个蹦床

我们的蹦床将只是一个矩形。在创建蹦床之前,做一些快速的 Javascript 设置以便于代码阅读。我们将拥有以下全局变量,您将看到我们如何在使用它们的过程中。我们将 Canvas 称为 drawArea,将上下文称为 BobContext。

var drawArea, BobContext;
var gameHandle = 0;
var bRightBut = false;
var bLeftBut = false;
var objBob, objTrampoline, objTargetBalloons;
var playPoints = 0;
var tickTock;
var playTime = iMin = iSec = 0;
var prevTickTock, prevScore;
var curvature = (4 * (Math.sqrt(2) - 1)) / 3;
var w_factor = 0.0333;
var h_factor = 0.4;
var tie_w_factor = 0.12;
var tie_h_factor = 0.10;
var tie_curve_factor = 0.13;
var grad_factor = 0.3;
var grad_rad = 3;
var bob = new Image();
var refreshRate = 10;
var clickCounter = 1;
var refreshCounter = 0;
var hitX = -1;
var hitY = -1; var hitRad = -1;

我们还将为蹦床创建一个类,以便更容易访问其属性。

function Trampoline(x, w, h) {
    this.x = x; //X position
    this.w = w; //Width
    this.h = h; //Height
}


清除方法

function clear() {
    BobContext.clearRect(0, 0, BobContext.canvas.width, BobContext.canvas.height);
    BobContext.fillStyle = '#fff';
    BobContext.fillRect(0, 0, BobContext.canvas.width, BobContext.canvas.height);
}


由于我们需要为这个游戏制作动画,我们将每 10 毫秒刷新一次屏幕,并在每次刷新时以新的位置重新绘制我们的形状,以创造运动的错觉。
为了每隔一定 *时间单位* 刷新屏幕,Java 脚本提供了两种简单的方法。

它们解释如下。

Javascript 定时器方法

setInterval 和 clearInterval 方法

setInterval 方法

此方法每隔指定的毫秒数执行指定的方法。此方法返回一个变量,该变量将是此定时器的句柄,稍后将用于暂停或停止定时器。

Syntax: var1 = setInterval("javascript_method",milliseconds);

clearInterval 方法

此方法需要一个在我们启动计时器时收到的句柄,并使用该句柄停止计时器,之后将不再有任何新的执行。

Syntax: clearInterval(var1)

我们还需要捕获键盘按键和鼠标。我们将使用以下函数来捕获 Canvas 区域内的鼠标移动以及仅当按下的键是左箭头或右箭头时的按键事件。

  $(window).keydown(function (event) { // keyboard-down alerts
        switch (event.keyCode) {
            case 37: // 'Left' key
                bLeftBut = true;
                break;
            case 39: // 'Right' key
                bRightBut = true;
                break;
        }
    });
    $(window).keyup(function (event) { // keyboard-up alerts
        switch (event.keyCode) {
            case 37: // 'Left' key
                bLeftBut = false;
                break;
            case 39: // 'Right' key
                bRightBut = false;
                break;
        }
    });
var iCanvX1 = $(drawArea).offset().left;
    var iCanvX2 = iCanvX1 + width;
    $('#scene').mousemove(function (e) { // binding mousemove event
        if (e.pageX > iCanvX1 && e.pageX < iCanvX2) {
            objTrampoline.x = Math.max(e.pageX - iCanvX1 - (objTrampoline.w / 2), 0);
            objTrampoline.x = Math.min(BobContext.canvas.width - objTrampoline.w, objTrampoline.x);
        }
    });


如果您查看上面的代码,您会注意到鼠标移动事件直接改变了蹦床的 X 值,因此当我们每 10 毫秒刷新屏幕时,我们的蹦床将绘制在新 x 位置。这样,您会感觉蹦床正在实时响应鼠标移动。而且,您可能已经猜到,蹦床的 Y 位置将始终保持不变。

同样,对于键盘事件,我们根据按下的键设置了两个变量,butLeft 和 butRight。我们将在刷新方法中使用这些条件重新计算蹦床的 X 值。例如,如果按下左键,我们将通过从蹦床的当前 X 值中减去 50 像素来向左移动蹦床。
现在让我们回到游戏的核心。refreshScreen 方法,我们将在其中放置所有绘图、计算和逻辑。目前,让我们只创建此方法来捕获蹦床的位置并根据鼠标移动和按键绘制它。

function refreshScreen() {
    clear();
    if (bRightBut)
        objTrampoline.x += 5; //Move Trampoline by 5 pixels
    else if (bLeftBut)
        objTrampoline.x -= 5; //Move Trampoline by 5 pixels
    BobContext.fillStyle = '#000';
    BobContext.beginPath();
    BobContext.rect(objTrampoline.x, BobContext.canvas.height - objTrampoline.h, objTrampoline.w, objTrampoline.h);
    BobContext.closePath();
}


下面还有两个方法,用于在加载时设置计时器,并每隔 10 毫秒调用上述 refreshScreen 方法。上述鼠标和键盘事件将进入 init() 方法。

//Document ready/On-Load function
$(function () {
    init();
});
//Initialisation
function init() {
    drawArea = document.getElementById('scene');
    BobContext = drawArea.getContext('2d');
    BobContext.clearRect(0, 0, drawArea.width, drawArea.height);
    var width = drawArea.width;
    var height = drawArea.height;
    objTrampoline = new Trampoline(width / 2, 120, 8); // new Trampoline object
    gameHandle = setInterval(refreshScreen, refreshRate); // loop re Draw Canvas
    $(window).keydown(function (event) { // keyboard-down alerts
        switch (event.keyCode) {
            case 37: // 'Left' key
                bLeftBut = true;
                break;
            case 39: // 'Right' key
                bRightBut = true;
                break;
        }
    });
    $(window).keyup(function (event) { // keyboard-up alerts
        switch (event.keyCode) {
            case 37: // 'Left' key
                bLeftBut = false;
                break;
            case 39: // 'Right' key
                bRightBut = false;
                break;
        }
    });
    var iCanvX1 = $(drawArea).offset().left;
    var iCanvX2 = iCanvX1 + width;
    $('#scene').mousemove(function (e) { // binding mousemove event
        if (e.pageX > iCanvX1 && e.pageX < iCanvX2) {
            objTrampoline.x = Math.max(e.pageX - iCanvX1 - (objTrampoline.w / 2), 0);
            objTrampoline.x = Math.min(BobContext.canvas.width - objTrampoline.w, objTrampoline.x);
        }
    });
}

就是这样,下面是一个 GIF 图像,展示了它现在的样子。

让我们制作一些气球,为此我们将使用贝塞尔曲线方法。

下面是一个气球类。

function Balloon(centerX, centerY, radius, color) {
    this.centerX = centerX;
    this.centerY = centerY;
    this.radius = radius * .85;
    this.baseColor = color;  
}

以及绘制气球的方法。我已在代码中添加了注释,以便于理解。

Balloon.prototype.draw = function () {
    var centerX = this.centerX;
    var centerY = this.centerY;
    var radius = this.radius;
    var handleLength = curvature * radius; //Curvature = [(4 * (Math.sqrt(2) - 1)) / 3]
    var widthDiff = (radius * w_factor); //width factor is a constant of 0.333 defined as global
    var heightDiff = (radius * h_factor); //height factor is a constant of 0.4 defined as global -- Height will be larger than width to make it a Baloon else would become a circular balloon
    var balloonBottomY = centerY + radius + heightDiff; //Calculating the Bottom point of our Baloon by adding the centre Y, the Radies and the Height difference which was obtained from height factor
   
    BobContext.beginPath();
    // Section to draw the Top Left Curve
    var topLeftCurveStartX = centerX - radius;
    var topLeftCurveStartY = centerY;
    var topLeftCurveEndX = centerX;
    var topLeftCurveEndY = centerY - radius;
    BobContext.moveTo(topLeftCurveStartX, topLeftCurveStartY);
    BobContext.bezierCurveTo(topLeftCurveStartX, topLeftCurveStartY - handleLength - widthDiff,
        topLeftCurveEndX - handleLength, topLeftCurveEndY,
        topLeftCurveEndX, topLeftCurveEndY); // The 2 Control points are placed in a way to get a bigger arc on the top
    // Section to draw the Top Right Curve
    var topRightCurveStartX = centerX;
    var topRightCurveStartY = centerY - radius;
    var topRightCurveEndX = centerX + radius;
    var topRightCurveEndY = centerY;
    BobContext.bezierCurveTo(topRightCurveStartX + handleLength + widthDiff, topRightCurveStartY,
        topRightCurveEndX, topRightCurveEndY - handleLength,
        topRightCurveEndX, topRightCurveEndY);  // The 2 Control points are placed in a way to get a bigger arc on the top 
    // Section to draw the Bottom Right Curve
    var bottomRightCurveStartX = centerX + radius;
    var bottomRightCurveStartY = centerY;
    var bottomRightCurveEndX = centerX;
    var bottomRightCurveEndY = balloonBottomY;
    BobContext.bezierCurveTo(bottomRightCurveStartX, bottomRightCurveStartY + handleLength,
        bottomRightCurveEndX + handleLength, bottomRightCurveEndY,
        bottomRightCurveEndX, bottomRightCurveEndY);  // The 2 Control points are placed in a way to get a a smaller curve at the bottom
    // Section to draw the Bottom Left Curve
    var bottomLeftCurveStartX = centerX;
    var bottomLeftCurveStartY = balloonBottomY;
    var bottomLeftCurveEndX = centerX - radius;
    var bottomLeftCurveEndY = centerY;
    BobContext.bezierCurveTo(bottomLeftCurveStartX - handleLength, bottomLeftCurveStartY,
        bottomLeftCurveEndX, bottomLeftCurveEndY + handleLength,
        bottomLeftCurveEndX, bottomLeftCurveEndY);  // The 2 Control points are placed in a way to get a a smaller curve at the bottom
    BobContext.fillStyle = this.baseColor;
    BobContext.fill();
    // End balloon path
    // Create balloon tie
    var halfTieWidth = (radius * tie_w_factor) / 2;
    var tieHeight = (radius * tie_h_factor);
    var tieCurveHeight = (radius * tie_curve_factor);
    BobContext.beginPath();
    BobContext.moveTo(centerX - 1, balloonBottomY);
    BobContext.lineTo(centerX - halfTieWidth, balloonBottomY + tieHeight);
    BobContext.quadraticCurveTo(centerX, balloonBottomY + tieCurveHeight,
        centerX + halfTieWidth, balloonBottomY + tieHeight);
    BobContext.lineTo(centerX + 1, balloonBottomY); // Quadratic Curve to make a slightly curved triangle at the bottom
    BobContext.fill();
}

现在我们有了 Balloon 类及其 draw 方法,让我们继续创建一个半径为 100,位于 100,100 (x,y) 的 Balloon。让我们选择橙色。

    var balloon1 = new Balloon(100, 100, 100, 'FF9900'); //create a object of Balloon class
    balloon1.draw(0); //Call the method of Balloon class for the above object

下面是它的样子。

是的..!它确实看起来像个气球,但它并没有真正的气球外观,不是吗?

为了实现这一点,HTML 为我们提供了创建渐变的方法。

Canvas 渐变、图像

渐变类似于我们在上一篇文章中学习的 CSS 渐变。我们有两种 Canvas 渐变方法,即 LinearGradient 和 RadialGradient。

createLinearGradient()

此方法创建一个渐变对象。但是,定义渐变不会在画布上绘制任何东西。它只是存储在内存中的一个对象。要应用渐变,您需要将 fillStyle 设置为上面创建的渐变对象,并绘制一个形状,例如矩形或线条。

Syntax: context.createLinearGradient(x1,y1,x2,y2);
创建线性渐变参数
参数 描述
x1 这是渐变起点的 x 坐标值
y1 这是渐变起点的 y 坐标值
x2 这是渐变终点的 x 坐标值
y2 这是渐变终点的 y 坐标值

但是,我们还需要添加颜色停止点才能真正获得渐变效果。我们将不得不使用以下方法来实现。

addColorStop()

此方法指定渐变对象中的颜色和位置。

Syntax: gradient.addColorStop(stop,color);

这里的渐变是我们之前使用 createLinearGradient 方法创建的对象。

停止值介于 0.0 和 1.0 之间。使用这些值,我们可以在渐变上定义任意数量的颜色。让我们看一个创建彩虹般渐变并将其应用于矩形的示例。

var c=document.getElementById("HTMLCanvas");
var bobContext=c.getContext("2d");
var grd=bobContext.createLinearGradient(0,0,200,0);
grd.addColorStop(0,"black");
grd.addColorStop("0.3","magenta");
grd.addColorStop("0.5","blue");
grd.addColorStop("0.6","green");
grd.addColorStop("0.8","yellow");
grd.addColorStop(1,"red");
bobContext.fillStyle=grd;
bobContext.fillRect(10,10,200,100);

结果如下

createRadialGradient()

此方法创建径向或圆形渐变,而不是线性图案

Syntax: context.createRadialGradient(x1,y1,r1,x2,y2,r2);
创建径向渐变参数
参数 描述
x1 这是渐变起点的 x 坐标值
y1 这是渐变起点的 y 坐标值
r1 最外层圆的半径
x2 这是渐变终点的 x 坐标值
y2 这是渐变终点的 y 坐标值
r2 最内层圆的半径

与线性渐变一样,径向渐变也需要定义一组颜色停止点。

所以让我们使用上面的例子,只将线性渐变更改为径向渐变,通过添加半径。半径 100 将定义在 1.0 停止点定义的颜色的最外层半径,而 10 将是在 0.0 颜色停止点定义的内层颜色的半径。

var grd=bobContext.createRadialGradient(100,50,100,100,50,10);

结果如下。由于黑色半径为100,它已经超出了我们的矩形,不足以容纳整个渐变。



所以现在对于我们的气球,让我们使用径向渐变。我们所要做的就是在外边缘应用较深的基色,并随着向内移动逐渐变亮。

为了获得更亮和更深的颜色,让我们使用下面的方法,它根据我们传入的 0 到 1 之间的值返回更亮或更深的颜色代码。

function ColorLuminance(hex, lum) {
    // validate hex string
    hex = String(hex).replace(/[^0-9a-f]/gi, '');
    if (hex.length < 6) {
        hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
    }
    lum = lum || 0;
    // convert to decimal and change luminosity
    var rgb = "#",
        c, i;
    for (i = 0; i < 3; i++) {
        c = parseInt(hex.substr(i * 2, 2), 16);
        c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
        rgb += ("00" + c).substr(c.length);
    }
    return rgb;
}

在我们的 Balloon 类中,让我们添加一些数据成员来保存这些更亮和更深的颜色代码。

function Balloon(centerX, centerY, radius, color) {
    this.centerX = centerX;
    this.centerY = centerY;
    this.radius = radius * .85;
    this.baseColor = ColorLuminance(color, 0);
    this.darkColor = ColorLuminance(color, -0.3);
    this.lightColor = ColorLuminance(color, 0.3);
}

我们的 Draw 方法现在应该创建一个渐变并将其应用于气球。

    var gradientOffset = (radius / 3);
    var balloonGradient =
        BobContext.createRadialGradient(centerX + gradientOffset, centerY - gradientOffset,
            grad_rad,
            centerX, centerY, radius + heightDiff);
    balloonGradient.addColorStop(0, this.lightColor);
    balloonGradient.addColorStop(0.7, this.darkColor);
    BobContext.fillStyle = balloonGradient;
    BobContext.fill();

就是这样。让我们看看我们的气球现在是什么样子。


瞧!!现在,我们谈论的是什么。这看起来更像一个 3D 图像而不是 2D 图像,不是吗?

由于我们需要更多的气球来瞄准,让我们创建一个数组来容纳所有这些气球,并将它们在 5 行中并排绘制。所以我们的数组将是 5 行,以及我们可以容纳的任意数量的列。下面是 TargetBalloons 类,它将包含一个 Balloon 对象数组。

function TargetBalloons(w, h, r, c, p) {
    this.w = w;
    this.h = h;
    this.r = r; // Number of Rows
    this.c = c; // Number of Columns
    this.p = p; // padding
    this.objs; //Array of Balloon Objects
    this.colorCode; // Random colors for the balloons
    this.colors = ['9d9d9d', 'f80207', 'feff01', '0072ff', 'fc01fc', '03fe03', 'FF9900', '99CC00', '99FFFF', '330033', 'fff']; //Available colors for the balloons
}

让我们在 Init() 方法中填充这个数组。

objTargetBalloons = new TargetBalloons((width / 8) - 1, 20, 6, 8, 2); // new TargetBalloons object
    objTargetBalloons.objs = new Array(objTargetBalloons.r); // fill-in TargetBalloons
    objTargetBalloons.colorCode = new Array(objTargetBalloons.r);
    for (i = 0; i < objTargetBalloons.r; i++) {
        objTargetBalloons.objs[i] = new Array(objTargetBalloons.c);
        objTargetBalloons.colorCode[i] = new Array(objTargetBalloons.c);
        for (j = 0; j < objTargetBalloons.c; j++) {
            objTargetBalloons.objs[i][j] = 1; //1= Active and 0=Burst/Inactive
            objTargetBalloons.colorCode[i][j] = Math.ceil(Math.random() * 11); //Select Random color code out of the 11 available colors
        }
    }

接下来,我们必须遍历这个数组,并在 refreshScreen 方法中创建我们的气球。

    // Create Balloons (from array of its objects)
    for (i = 0, k = 25; i < objTargetBalloons.r; i++) {
        if (k == 0)
            k = 25; //Spacing for alternate rows
        else
            k = 0;
        for (j = 0; j < objTargetBalloons.c; j++) 
            {
                var balloon1 = new Balloon(k + (j * (objTargetBalloons.w + objTargetBalloons.p)) + objTargetBalloons.p + objTargetBalloons.w / 4, (i * (objTargetBalloons.h + objTargetBalloons.p)) + objTargetBalloons.p,                  objTargetBalloons.w / 2, objTargetBalloons.colors[objTargetBalloons.colorCode[i][j]]);                
                 balloon1.draw();
                }
            }
        }
    }

既然我们现在了解了渐变,让我们也为我们的蹦床应用一个线性渐变。

// Create Trampoline
    var trampolineGradient = BobContext.createLinearGradient(objTrampoline.x, BobContext.canvas.height - objTrampoline.h,
                                                             objTrampoline.x + objTrampoline.w, (BobContext.canvas.height - objTrampoline.h));
    trampolineGradient.addColorStop(0, "gray");
    trampolineGradient.addColorStop(0.5, "white");
    trampolineGradient.addColorStop(1.0, "gray");
    BobContext.fillStyle = trampolineGradient;

我们的屏幕上充满了漂亮的气球。

现在舞台已经准备好了,让我们欢迎鲍勃进入竞技场,协助我们戳破这些气球。

要在 Canvas 上绘制图像,HTML 提供了一个 drawImage() 方法。

DrawImage()

正如我们之前看到的,此方法用于在 Canvas 上绘制图像。因此,无需多言,让我们直接进入语法和示例。

此方法可以接受 3、5 或 9 个参数。

drawImage(image, x, y): 这是所有方法中最简单的,它接受一个图像并将其绘制在给定的 x 和 y 坐标处。x 和 y 将是图像的左上角。x,y = (0, 0) 的值会将图像绘制在 Canvas 的左上角。这也会以其原始分辨率绘制图像。

drawImage(image, x, y, w, h): 此方法与上述功能相同,但它会将图像缩放到给定的宽度[w]和高度[h]。

drawImage(image, clipX, clipY, clipW, clipH, x, y, scaleW, scaleH): 此方法接受一个图像,将其剪裁到矩形 (clipX, clipY, clipW, clipH),将其缩放到尺寸 (scaleW, scaleH),并将其绘制在 Canvas 的坐标 (x, y) 处。

示例

var canvas = document.getElementById("e");
var context = canvas.getContext("2d");
var bob = new Image();
cat.src = "images/bob.png";
context.drawImage(bob, sx=2, sy=2, sw=3, sh=3, dx,=4 dy=4, dw=4, dh=4) 

下面是上述示例将如何呈现的表示

但是,如果您需要在 Canvas 上重复图像怎么办?

例如,您需要为 Canvas 创建一个带有图像的背景,您可以循环绘制相同的图像,直到填充整个 Canvas。嗯,HTML 有一种更简单、更优雅的方法。

它提供了一个名为 **createPattern()** 的方法,该方法以指定方向重复指定元素。在我们的例子中,这个元素将是一个图像。

Syntax: context.createPattern(image,"repeat-value");

参数

描述

图像

定义用于图案的元素,可以是图像、Canvas 或视频元素

重复值

此参数定义图案在 Canvas 上的重复方向。以下是此参数接受的值:

重复

这是默认值,它水平和垂直重复元素

repeat-x

图案仅水平重复元素

repeat-y

图案仅垂直重复元素

no-repeat

图案将只显示元素一次

让我们快速看一个用一个小图像创建背景的示例,该图像水平和垂直重复。

让我们创建一个名为 imgBG(图像背景)的变量,并在 init() 方法中为其分配一个图像。

var imgBG = new Image(); 
imgBG.src = 'images/pattern.jpg';

在我们的 clear() 方法中,让我们每次清除屏幕后都使用 createPattern() 和 drawimage() 方法绘制此图像。

function clear() {
    BobContext.clearRect(0, 0, BobContext.canvas.width, BobContext.canvas.height);
    var BGpattern = BobContext.createPattern(imgBG, 'repeat');
    BobContext.rect(0, 0, drawArea.width, drawArea.height);
    BobContext.fillStyle = BGpattern;
    BobContext.fill();
}

结果将如下所示

是时候让鲍勃进入我们的 Canvas 了。下面是保存鲍勃位置的类。

function Bobby(x, y, dx, dy, r) {
    this.x = x; //x value
    this.y = y; //y value
    this.dx = dx; //change in x when moving/bouncing
    this.dy = dy; // change in y when moving/bouncing
    this.r = r; //radius of Bob’s presence
}

在我们的 Init 方法中,让我们实例化一个 Bobby 类的对象并将其命名为 objBob,目前我们的 dx 和 dy 将为 0,半径将为 10。Bob 的初始位置将位于 Canvas 的底部中心。

objBob = new Bobby(width / 2, 550, 0, 0, 10);

在刷新屏幕中,让我们实际绘制鲍勃。

BobContext.drawImage(bob, objBob.x, objBob.y, 50, 50);

现在我们的屏幕将如下所示

所以我们大部分工作都完成了。现在唯一要做的就是让鲍勃四处弹跳并检测与墙壁和蹦床的碰撞。

下面的代码将检测碰撞并更改 dx 和 dy 值,以便每次刷新时都在新位置创建鲍比

首先,让我们在 init() 方法中创建 Bob 对象时,通过给定小的 dx 和 dy 值来启动 Bob 的移动。

objBob = new Bobby(width / 2, 550, 0.5, -5, 10); // new Bobby object

现在在我们的 refreshscreen() 方法中,让我们进行一些碰撞检测。

    // Evaluate if a Wall or Trampoline was hit
    iRowH = objTargetBalloons.h + objTargetBalloons.p;
    iRow = Math.floor(objBob.y / iRowH);
    iCol = Math.floor(objBob.x / (objTargetBalloons.w + objTargetBalloons.p));

    // Hit a wall!! Reverse Bob's direction
    if (objBob.x + objBob.dx + objBob.r > BobContext.canvas.width || objBob.x + objBob.dx - objBob.r < 0) {
        objBob.dx = -objBob.dx;
    }

    // Hit a Trampoline!! Reverse Bob's direction
    if (objBob.y + objBob.dy - objBob.r < 0) {
        
        objBob.dy = -objBob.dy;
    } else if (objBob.y + objBob.dy + objBob.r > BobContext.canvas.height - (objTrampoline.h + 25)) {
        if (objBob.x > objTrampoline.x && objBob.x < objTrampoline.x + objTrampoline.w) {
            objBob.dx = 10 * ((objBob.x - (objTrampoline.x + objTrampoline.w / 2)) / objTrampoline.w);
            objBob.dy = -objBob.dy;
        } else if (objBob.y + objBob.dy + objBob.r > BobContext.canvas.height) {
            clearInterval(gameHandle); //END GAME
        }
    }

    //If nothing was hit continue on Bob's current path
    objBob.x += objBob.dx;
    objBob.y += objBob.dy;


请注意,我们编写代码的方式是,如果鲍勃击中蹦床的左半部分,那么我们会创建一个向 Canvas 左侧倾斜的移动,类似地,如果他击中右侧,则向右倾斜。下面是实现此功能的条件。

objBob.dx = 10 * ((objBob.x - (objTrampoline.x + objTrampoline.w / 2))/ objTrampoline.w);

我们的鲍勃正在高兴地跳来跳去……你可以在下面看到他有多开心。

最后一部分是当气球被击中时使其爆裂。这非常简单,因为我们所要做的就是将气球数组元素的值设置为 0。由于我们以 8/5 网格创建气球,因此很容易检测鲍勃是否进入该区域,如果进入,只需将该 [i][j] 的活动值设置为 0。

下面是代码。我们还创建一个名为 playPoints 的变量,并在每次击中气球时递增它。

// 如果击中气球,则使其爆裂并反转鲍勃的方向
if (objBob.y < objTargetBalloons.r * iRowH && iRow >= 0 && iCol >= 0 && objTargetBalloons.objs[iRow][iCol] == 1) {
objBob.dy = -objBob.dy; // 反转鲍勃的 y 方向
playPoints++; // 分数增加一
objTargetBalloons.objs[i][j] = 0; // 将气球的活动值设置为 0,以便在下一次刷新时不再创建气球(10 毫秒后)
}

听起来不错,但我们也要尝试 Canvas 的更多方法,即 Translate、Rotate 和 Scale。

平移、旋转和缩放方法

平移

就像 CSS 中一样,在 Canvas 中,Translate 也用于通过 x 和 y 值移动对象。然而,Canvas 上的 translate 意味着将整个 Canvas 移动 x 和 y 值,并且在 translate 之后绘制的任何内容都将在新的 Canvas 上以 x 和 y 值进行偏移绘制。

Syntax: context.translate(x,y);

示例:下面是一个关于 translate 如何工作的快速表示。

旋转

一个简单的定义听起来像是“旋转方法以给定角度旋转画布”:)

就像平移一样,旋转会旋转整个画布而不是元素。此外,旋转的值范围应在 -90 到 +90 度之间,因为超出此范围会使画布旋转到其他象限,我们的绘图将不可见。

Syntax: context.rotate(angle);

角度值应以弧度表示。如前所述,要将度数转换为弧度,请使用以下公式:

弧度 = 度数 * Math.PI / 180。

例如,要旋转 45 度,请指定:*Context.rotate(45*Math.PI/180);*

下面是旋转如何工作的快速表示。请注意,Canvas(黄色框)之外的任何内容都不会显示,蓝线显示 Canvas 旋转了 45 度角。

本节的最后一个方法是 Scale(x,y)。

Scale

此方法与另外两种方法一样,影响整个 Canvas,并且在使用时会根据 x 和 y 值缩放 Canvas。当 x=2 且 y=2 时,Canvas 将缩放到原始大小的两倍。下面是 Canvas 应用缩放前后的表示。

Syntax: Context.scale(x,y);

为了避免在使用 translate/scale/rotate 方法后所有其他绘图都被移动,HTML 提供了两种方法来将 Canvas 恢复到其原始状态。

保存和恢复

在平移/旋转/缩放之前调用 save(),在平移/旋转/缩放之后立即调用 restore(),这样我们之后的所有绘图都将在正常坐标上绘制,而不是平移/旋转/缩放的坐标上绘制。

最后,我想讨论一个与在 Canvas 上绘制文本相关的属性和一个方法,这样您就可以重新玩代码,使这个游戏变得更酷。

字体属性和 FillText 方法

Context.Font

该属性被称为 font,并应用于 Context。它设置或获取 Canvas 上下文上文本内容的字体属性。

语法如下,与 CSS 中的 font 属性完全相同。

Syntax: context.font=font-style font-variant font-weight font-size font-face;

Example: context.font="italic small-caps bold 12px arial";

FillText() 方法

此方法用于在 Canvas 上创建或绘制文本。默认颜色为黑色,如果在调用此方法之前设置了 Font 属性,则会考虑该属性。

Syntax: context.fillText(text,x,y,maxWidth);

示例

var canvas1=document.getElementById("HTMLCanvas");
var bobContext=canvas1.getContext("2d");
bobContext.font="20px Georgia";
bobContext.fillText("Bob is Jumping!",20,70);
bobContext.font="30px Verdana";
// Let us reate gradient
var grd=bobContext.createLinearGradient(0,0,canvas1.width,0);
grd.addColorStop("0","Green");
grd.addColorStop("0.5","Red");
grd.addColorStop("1.0","Blue");
// set fill style to gradient
bobContext.fillStyle=grd;
bobContext.fillText("Bob is Jumping!",40,100);

我使用了上述方法,在鲍勃击中气球时移动、缩小、旋转气球,并显示鲍勃弹跳时的分数和 x,y 坐标。

我附上了最终代码,而不是将其添加在此处,以控制本文的长度 :)。但下面是一个快速 GIF 图像,看看它在屏幕上是什么样子。

我还添加了一些功能,可以在 Canvas 上单击暂停,并添加一个按钮随时重新开始游戏。您可能想尝试为单个游戏添加多条命甚至多人游戏功能。

作业:游戏 - Bobby Carter

作为一项作业,让我们创建一个名为 Bobby Carter 的简单游戏

我们的鲍勃现在有了一份新工作,那就是收集画布上的所有购物车。

然而,当他收集购物车时,他必须把它们拖在身后,这样购物车队伍会越来越长。目标是尽可能多地收集购物车,而不会撞到他自己的购物车队伍。

试试这个,如果你遇到困难,可以下载本文顶部附带的解决方案。

下面是我的解决方案结果,由于时间限制,它看起来非常简单,但欢迎您玩代码并使其变得更好、更酷。

使用代码

下载、加载和开始玩游戏三个简单步骤。Javascript 文件包含大部分代码,位于每个游戏的 Script 子文件夹中。

  1. 下载
  2. 解压
  3. 在 Chrome 中打开 HTML(我尚未在其他浏览器上测试过。因此,建议使用 Chrome)

关注点

我确实学到了一件事。创造游戏比玩游戏有趣得多。我相信你也会有同样的感觉。

我还要感谢作者 Franken Logan 帮助我制作出完美的气球。

历史

文章初稿 - Guru prasad K Basavaraju - 2014年4月27日

© . All rights reserved.