使用JavaScript和Canvas复制DOOM屏幕融化

    技术2022-07-11  132

    I love retro games almost as much as I love development and from time to time I find myself addicted to games I haven't played in 20 or more years. This weekend while loading up DOOM on my speedy 486/SX (a full 66mhz of speed!) I was reminded of the awesome screen melt effect when transitioning between menus and levels. From looking at it I really had no idea how it was accomplished, so seeing as DOOM is open source I went right to the source and I was surprised with how simple it is to achieve.

    我对复古游戏的热爱与对开发的热爱几乎一样,不时发现自己沉迷于20多年没有玩过的游戏。 这个周末,当我在快速的486 / SX上加载DOOM(速度高达66mhz!)时,我想起了在菜单和级别之间切换时令人赞叹的屏幕融化效果。 通过查看它,我真的不知道它是如何完成的,所以看到DOOM是开源的,我直接去了源代码,并且对实现它的简单性感到惊讶。

    So how exactly does the effect work? First you need to logically divide the screen into columns allowing them to be moved independently.

    那么效果到底如何发挥作用? 首先,您需要在逻辑上将屏幕分为几列,以使它们可以独立移动。

    Next Each column then needs to be assigned a height value that is less than 0. We start out by assigning the first column a random value between 0 and -100, and each neighboring column is assigned a random value within 50 of its neighbor. We also have a limit in place for the values, never allowing a value greater than 0, and never allowing a value less than our maximum deviation of -100.

    Next接下来,需要为每列分配一个小于0的高度值。我们首先为第一列分配一个介于0到-100之间的随机值,并且为每个相邻列分配一个在其相邻50范围内的随机值。 我们还对这些值设置了限制,从不允许大于0的值,也不允许小于我们最大偏差-100的值。

    These values aren't set in stone and can be played with, but the higher the deviation between columns the more random the effect will become. The reason behind keeping the columns values within a certain range of their neighbors is to create a rolling hill effect, this same method can also be used when creating simple 2d terrain.

    这些值不是一成不变的,可以使用,但列之间的偏差越大,效果将变得越随机。 将列值保持在相邻列的某个范围内的原因是要创建起伏的丘陵效果,创建简单的2D地形时也可以使用相同的方法。

    The next and final step is to lower the columns in order to reveal the image behind it. The "magic" of the melt effect is illustrated below. This should also make it clear why we need to assign negative values to begin with.

    下一步也是最后一步是降低列,以显示其后面的图像。 熔化效果的“魔术”如下所示。 这也应该使我们清楚为什么要开始分配负值。

    实作 (Implementation)

    When I implemented the effect I tried two different approaches direct pixel manipulation using getImageData and putImageData, and using standard drawImage with offsets. The drawImage approach was much faster and the method I'll be explaining.

    当实现效果时,我尝试了两种不同的方法,分别使用getImageData和putImageData以及带偏移的标准drawImage进行像素操纵。 drawImage方法要快得多,我将要说明的方法。

    We will use two images for the effect, the first image is the background and will be drawn first every tick, we will then draw the 2nd image in columns offsetting the y position of each column by its value incrementing the value every time the doMelt() function is called until all columns values are greater than the height of the image.

    我们将使用两张图像进行效果处理,第一张图像是背景图像,并且将在每个刻度上首先绘制,然后在每列中绘制第二张图像,每列的y位置偏移其值,每次doMelt()函数被调用,直到所有列的值都大于图像的高度为止。

    HTML (The HTML)

    The html needed is very minimal all we need is the canvas element

    所需的html非常小,我们需要的只是canvas元素

    <canvas id="canvas"></canvas>

    JavaScript (The JavaScript)

    For the melt effect we will create a canvas element in memory this is where we will draw the offset columns to, image1 and image2 hold references to image objects created within the js, bgImage and meltImage are used to swap between what image is the background and what image is melting.

    对于融合效果,我们将在内存中创建一个canvas元素,在此处将绘制偏移量列,image1和image2保留对在js中创建的图像对象的引用,bgImage和meltImage用于在背景图像和背景图像之间进行交换。什么图像正在融化。

    var meltCan = document.createElement("canvas"), meltCtx = meltCan.getContext("2d"), images = [image1, image2], bgImage = 1, meltImage = 0,

    The following settings are what will control how the resulting effect looks. colSize controls the width of the columns, maxDev controls the highest a column can go, maxDiff controls the maximum difference in value between neighboring columns, and fallSpeed controls how fast the columns fall.

    以下设置将控制结果效果的外观。 colSize控制列的宽度,maxDev控制列可以走的最高高度,maxDiff控制相邻列之间的最大差值,而fallSpeed控制列下降的速度。

    settings = { colSize: 2, maxDev: 100, maxDiff: 50, fallSpeed: 6, }

    The init() function is where we setup our columns initial values and draw the image we are going to melt to our temporary canvas. We set the first element to a random number that falls between 0 and maxDev, then for each neighboring column pick a random value thats within the maxDiff range we set.

    在init()函数中,我们设置列的初始值,并将要融合的图像绘制到临时画布上。 我们将第一个元素设置为介于0和maxDev之间的随机数,然后为每个相邻列选择一个在我们设置的maxDiff范围内的随机值。

    function init() { meltCtx.drawImage(images[meltImage],0,0); for (var x = 0; x < columns; x++) { if (x === 0) { y[x] = -Math.floor(Math.random() * settings.maxDev); } else { y[x] = y[x - 1] + (Math.floor(Math.random() * settings.maxDiff) - settings.maxDiff / 2); } if (y[x] > 0) { y[x] = 0; } else if (y[x] < -settings.maxDev) { y[x] = -settings.maxDev; } } }

    The doMelt() function is where the magic happens. First we draw our image thats behind the melting image to the canvas, another approach is to place the canvas element in front of an image and use clearRect to clear the canvas. However for this example we will just draw both images to the same canvas. Next we iterate through the columns incrementing their value by fallspeed. If the value is not greater than 0, it means the user cannot see the effect yet, so the columns y position (yPos) stays at 0. If the column value is greater than 0, the columns y position is set to the columns value. We then use drawImage to draw the column from the temporary canvas to the primary canvas using the offsetting its y by yPos.

    doMelt()函数就是神奇的地方。 首先,我们在融化的图像后面将图像绘制到画布上,另一种方法是将画布元素放置在图像前面,然后使用clearRect清除画布。 但是,对于此示例,我们将仅将两个图像绘制到同一画布上。 接下来,我们遍历各列,使其值以下降速度递增。 如果该值不大于0,则表示用户尚未看到效果,因此y列的位置(yPos)保持为0。如果该列值大于0,则将y列的位置设置为该列的值。 然后,我们使用drawImage通过将y偏移yPos将列从临时画布绘制到主画布。

    The done flag stays true if the column values are greater than the height, and we swap images to do it again.

    如果列值大于高度,则完成标志保持为true,我们交换图像以再次执行该操作。

    function doMelt() { ctx.drawImage(images[bgImage],0,0); done = true; for (col = 0; col < columns; col++) { y[col] += settings.fallSpeed; if (y[col] < 0 ) { done = false; yPos = 0; }else if(y[col] < height){ done = false; yPos = y[col]; } ctx.drawImage(meltCan, col * settings.colSize, 0, settings.colSize, height, col * settings.colSize, yPos, settings.colSize, height); } if(done){ var swap = meltImage; meltImage = bgImage; bgImage = swap; init(); } requestAnimationFrame(domelt); }

    The completed code and effect can be seen on CodePen: http://codepen.io/loktar00/details/vuiHw.

    完整的代码和效果可以在CodePen上看到: http ://codepen.io/loktar00/details/vuiHw。

    If you're curious as to how the masterminds of DOOM implemented the effect you can check it out at https://github.com/id-Software/DOOM/blob/master/linuxdoom-1.10/f_wipe.c

    如果您对DOOM的策划者如何实现效果感到好奇,可以在https://github.com/id-Software/DOOM/blob/master/linuxdoom-1.10/f_wipe.c中查看

    翻译自: https://davidwalsh.name/canvas-effect

    Processed: 0.011, SQL: 9