案例研究:在 Entanglement 游戏中 HTML5 Canvas 的使用

发布于 2023-05-13 13:00:03 字数 8610 浏览 84 评论 0

今年春天(2010年),我对 HTML5 和相关技术的快速增长的支持产生了兴趣。当时,我和一位朋友在为期两周的游戏开发竞赛中互相挑战,以磨练我们的编程和开发技能,并将我们不断相互抛出的游戏创意变为现实。因此,我自然而然地开始将HTML5元素纳入我的参赛作品中,以便更好地了解它们的工作原理,并能够完成使用早期HTML规范几乎不可能的事情。

在HTML5的许多新功能中,对canvas标签的支持越来越多,这为我提供了一个令人兴奋的机会,可以使用 JavaScript 实现交互式艺术,这促使我尝试实现一个现在称为 Entanglement 的益智游戏。我已经使用卡坦岛的定居者瓷砖的背面创建了一个原型,因此将其用作某种蓝图,在HTML5画布上塑造六边形瓷砖以进行Web游戏有三个基本部分:绘制六边形,绘制路径和旋转瓷砖。下面详细介绍了我是如何以当前形式完成其中的每一个的。

绘制六边形

在 Entanglement 的原始版本中,我使用了几种画布绘制方法来绘制六边形,但游戏的当前形式使用 drawImage() 来绘制从精灵表上剪裁的纹理。

瓷砖精灵表

瓷砖精灵表

我将图像组合成一个文件,因此它只是向服务器发出一个请求,而不是在本例中为十个请求。要将选定的六边形绘制到画布上,我们首先必须将我们的工具聚集在一起:画布、上下文和图像。

要创建画布,我们只需要 html 文档中的 canvas 标签,如下所示:

<canvas></canvas>

我给它一个 id,以便我们可以将其拉入我们的脚本中:

var cvs = document.getElementById('myCanvas');

其次,我们需要获取画布的 2D 上下文,以便我们可以开始绘制:

var ctx = cvs.getContext('2d');

最后,我们需要图像。如果它被命名为“tiles.png”,与我们的网页位于同一文件夹中,我们可以通过以下方式获取它:

var img = new Image();
img.src = 'tiles.png';

现在我们有了三个组件,我们可以使用 ctx.drawImage() 将我们想要的单个六边形从精灵表绘制到画布上:

ctx.drawImage(img, sourceX, sourceY, sourceWidth, sourceHeight,
              destinationX, destinationY, destinationWidth, destinationHeight);

在本例中,我们使用顶行左侧的第四个六边形。此外,我们会将其绘制到左上角的画布上,使其与原始大小保持一致。假设六边形宽 400 像素,高 346 像素,总共看起来像这样:

var cvs = document.getElementById('myCanvas');
var ctx = cvs.getContext('2d');
var img = new Image();
img.src = 'tiles.png';
var sourceX = 1200;
var sourceY = 0;
var sourceWidth = 400;
var sourceHeight = 346;
var destinationX = 0;
var destinationY = 0;
var destinationWidth = 400;
var destinationHeight = 346;
ctx.drawImage(img, sourceX, sourceY, sourceWidth, sourceHeight,
              destinationX, destinationY, destinationWidth, destinationHeight);

我们已经成功地将部分图像复制到画布上,结果如下:

六角形瓷砖 六角形瓷砖

六角形瓷砖

绘制路径

现在我们已经将六边形绘制到画布上,我们想在画布上画几条线。首先,我们将查看有关六边形图块的一些几何图形。我们希望每边有两个线端,每个端点从沿每个边的端点开始 1/4,边缘的 1/2 彼此分开,如下所示:

六边形磁贴上的线端点 六

六边形磁贴上的线端点

我们还想要一条漂亮的曲线,所以,通过一些试验和误差,我发现,如果我从每个端点的边缘做一条垂直线,每对端点围绕六边形给定角度的交点为给定端点提供了一个很好的贝塞尔控制点:

六边形瓷砖上的控制点 六边

六边形瓷砖上的控制点

现在,我们将端点和控制点映射到与我们的画布图像对应的笛卡尔平面,我们准备回到代码。为了简单起见,我们将从一行开始。我们将首先绘制一条从左上角端点到右下端点的路径。由于我们之前的六边形图像为 400x346,这将使我们的顶部端点横向为 150 像素,向下 0 像素,简写 (150, 0)。它的控制点将是(150,86)。底部边缘端点为 (250, 346),控制点为 (250, 260):

第一条贝塞尔曲线的坐标 第一条贝塞尔曲线的

第一条贝塞尔曲线的坐标

有了坐标,我们现在准备开始绘制。我们将从 ctx.beginPath() 重新开始,然后使用以下方法移动到第一个端点:

ctx.moveTo(pointX1,pointY1);

然后,我们可以使用 ctx.bezierCurveTo() 绘制直线本身,如下所示:

ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);

由于我们希望线条有一个漂亮的边框,我们将每次使用不同的宽度和颜色笔触此路径两次。将使用 ctx.strokeStyle 属性设置颜色,并使用 ctx.lineWidth 设置宽度。总之,绘制第一行将如下所示:

var pointX1 = 150;
var pointY1 = 0;
var controlX1 = 150;
var controlY1 = 86;
var controlX2 = 250;
var controlY2 = 260;
var pointX2 = 250;
var pointY2 = 346;
ctx.beginPath();
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();

我们现在有一个六边形的瓷砖,第一行蜿蜒穿过:

六角形瓷砖上的孤线 六角形瓷砖上的孤

六角形瓷砖上的孤线

输入其他 10 个端点的坐标以及相应的贝塞尔曲线控制点,我们可以重复上述步骤,并可能创建一个如下所示的磁贴:

完成的六角形瓷砖

旋转画布

一旦我们有了我们的瓷砖,我们希望能够转动它,以便在游戏中采取不同的路径。为了使用 canvas 来实现这一点,我们使用 ctx.translate() 和 ctx.rotate()。我们希望磁贴围绕其中心旋转,因此第一步是将画布参考点移动到六边形磁贴的中心。为此,我们使用:

ctx.translate(originX, originY);

其中 originX 将是六边形图块宽度的一半,原点 Y 将是高度的一半,从而得到:

var originX = 200;
var originY = 173;
ctx.translate(originX, originY);

现在,我们能够使用新的中心点旋转磁贴。由于六边形有六个边,我们需要将其旋转 Math.PI 的倍数除以 3。我们将保持简单,并使用以下命令顺时针旋转一圈:

ctx.rotate(Math.PI / 3);

但是,由于我们的六边形和线使用旧的 (0,0) 坐标作为原点,因此一旦我们完成旋转,我们将希望在绘制之前平移回来。所以,我们现在总共有:

var originX = 200;
var originY = 173;
ctx.translate(originX, originY);
ctx.rotate(Math.PI / 3);
ctx.translate(-originX, -originY);

将上述平移和旋转放在渲染代码之前会导致它现在呈现旋转的磁贴:

旋转的六角形磁贴 旋转的六角形磁贴

旋转的六角形瓷砖

总结

上面我重点介绍了HTML5使用canvas标签必须提供的一些功能,包括渲染图像,绘制贝塞尔曲线和旋转画布。使用 HTML5 canvas 标签及其 JavaScript 绘图工具进行 Entanglement 被证明是一种愉快的体验,我期待着其他人使用这种开放和新兴技术创建的许多新应用程序和游戏。

代码参考

下面结合上面提供的所有代码示例作为参考:

var cvs = document.getElementById('myCanvas');
var ctx = cvs.getContext('2d');
var img = new Image();
img.src = 'tiles.png';

var originX = 200;
var originY = 173;
ctx.translate(originX, originY);
ctx.rotate(Math.PI / 3);
ctx.translate(-originX, -originY);

var sourceX = 1200;
var sourceY = 0;
var sourceWidth = 400;
var sourceHeight = 346;
var destinationX = 0;
var destinationY = 0;
var destinationWidth = 400;
var destinationHeight = 346;
ctx.drawImage(img, sourceX, sourceY, sourceWidth, sourceHeight,
              destinationX, destinationY, destinationWidth, destinationHeight);

ctx.beginPath();
var pointX1 = 150;
var pointY1 = 0;
var controlX1 = 150;
var controlY1 = 86;
var controlX2 = 250;
var controlY2 = 260;
var pointX2 = 250;
var pointY2 = 346;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();

ctx.beginPath();
pointX1 = 250;
pointY1 = 0;
controlX1 = 250;
controlY1 = 86;
controlX2 = 150;
controlY2 = 86;
pointX2 = 75;
pointY2 = 43;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();

ctx.beginPath();
pointX1 = 150;
pointY1 = 346;
controlX1 = 150;
controlY1 = 260;
controlX2 = 300;
controlY2 = 173;
pointX2 = 375;
pointY2 = 213;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();

ctx.beginPath();
pointX1 = 325;
pointY1 = 43;
controlX1 = 250;
controlY1 = 86;
controlX2 = 300;
controlY2 = 173;
pointX2 = 375;
pointY2 = 130;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();

ctx.beginPath();
pointX1 = 25;
pointY1 = 130;
controlX1 = 100;
controlY1 = 173;
controlX2 = 100;
controlY2 = 173;
pointX2 = 25;
pointY2 = 213;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();

ctx.beginPath();
pointX1 = 325;
pointY1 = 303;
controlX1 = 250;
controlY1 = 260;
controlX2 = 150;
controlY2 = 260;
pointX2 = 75;
pointY2 = 303;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

反话

暂无简介

文章
评论
866 人气
更多

推荐作者

mb_XvqQsWhl

文章 0 评论 0

我不在是我

文章 0 评论 0

依 靠

文章 0 评论 0

L.W.

文章 0 评论 0

暗里之光

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文