📜 ⬆️ ⬇️

Spectacular animation of destruction (Pixel Dust) on JavaScript

In the process of developing our game on HTML5, we are faced with a dilemma: draw a destructive effect for each element or try to do it programmatically on JavaScript (canvas). If everything is clear with the first method (it works with confidence, but there is a lot of work for the artist), then with the second we had doubts about the rendering speed, because it is 60FPS x 64 x 4 bytes ~ 1 MB / s. on one element, and if there are 40 on one screen?



So, the task has been set: to create an effect for the game based on HTML5 (canvas), the effect is to take an image size from 32x32 to 64x64 pixels on input and generate a sequence of frames that will play at 60FPS. It would seem possible to cache this in order not to load the processor, but 60FPS x 64 width x 64 height x 4 bytes per pixel is almost a megabyte, and it is only for one second for one input image. Imagine that the effect should be applied to hundreds of images, and it lasts more than a second - you will not get enough memory. It remains a realtime calculation, so let's talk about it.
')
The idea is to split the input raster into small pieces of 2x2 or 4x4 pixels, and at each drawing calculate their new position, positioning in the resulting raster. The components of the color and the alpha channel should also depend on time according to some quadratic law. In my case, the color of the heap tends to 0x323232, alpha to 1.0, the direction of ripping pixels (left / right) to transparency (alpha 0).

For the waving effect, two types of particles are required: some will fly off to one side, others will settle and form a heap. In addition, for each particle, some random departure speed is needed. To make it look not quite random, both random variables can be taken from the Perlin noise.

As it turned out, V8 is fast enough to simultaneously support 40 such effects for images with an average size of 48x48 with full 60FPS. ( look at this )



Effect code with detailed comments
//       var test = [], test2 = [] for (var i = 0; i < 256; i++) { test.push(0); test2.push(0); } //  - Effect = function () { this.buffer = document.createElement('canvas'); }; // ****       -,    //       (new Effect()) Effect.prototype.ready = function () { return this.progress >= 0.99; } //    // w, h -       // part -     // dir -   , -1 , 0  , 1  //               , //     this.w2 //        Effect.prototype.init0 = function (w, h, part, dir) { this.buffer.width = dir == 0 ? w : 2 * w; this.buffer.height = h; this.dir = 0; this.sx = 0; this.w = w; this.w2 = w; this.h = h; this.dir = dir; if (dir == -1) { this.sx = w; this.w *= 2; } if (dir == 1) { this.w *= 2; } this.wp = w / part; this.hp = h / part; this.part = part; return this.buffer; } //      ,      (x, y) Effect.prototype.init1 = function (x, y) { var context = this.buffer.getContext("2d"); //    var data = context.createImageData(this.w, this.h); //   var orig = context.getImageData(0, 0, this.w2, this.h); //  : 0 -  , 1 -  , 2 -   , 3 -    var parts = []; //     ,    1 var px = [], py = []; //  ,      var vx = [], speed = []; //           var noise = GenerateRandom(this.wp, this.hp); var k = 0; var part = this.part; for (var j = this.hp - 1; j >= 0; j--) { for (var i = 0; i < this.wp; i++) { var x0 = i * part; var y0 = j * part; var c = 0; for (var dx = 0; dx < part; dx++) for (var dy = 0; dy < part; dy++) { var t = (x0 + dx) + (y0 + dy) * this.w2; if (orig.data[t * 4 + 3] != 0) { c++; } } var r = noise[k++] px.push(x0 + this.sx); py.push(y0); //      if (c * 2 >= part * part) { speed.push(1.2 * r + 0.75); if (r > 0.5) { parts.push(3); } else { parts.push(2); } } else { speed.push(0); parts.push(0); } } } //     this.level = []; for (var i = 0; i < this.w; i++) this.level.push(this.h); this.parts = parts; this.vx = vx; this.speed = speed; this.px = px; this.py = py; this.context = context; this.data = data; this.orig = orig; this.progress = 0.0; this.x = x; this.y = y; return this; } //     img,    Effect.prototype.init01 = function (x, y, w, h, part, dir, img, imgX, imgY, imgW, imgH) { this.init0(w, h, part, dir); var context = this.buffer.getContext("2d"); context.drawImage(img, imgX, imgY, imgW, imgH, 0, 0, w, h); this.init1(x, y); return this; } //               Effect.prototype.draw = function (context, x, y, w, h) { if (w === undefined) { w = this.w2; h = this.h; } if (this.dir == -1) { x -= w; w *= 2; } if (this.dir == 1) { w *= 2; } context.drawImage(this.buffer, x, y, w, h); } //    , progress -       , this.progress  0.0  1.0 Effect.prototype.update = function (progress) { this.progress += progress; var c = 100; var data = this.data.data; var orig = this.orig.data; var wp = this.wp; var hp = this.hp; var part = this.part; var h = this.h; var w = this.w; var k = 0; var w2 = this.w2; // test -   , test2 -    var p = this.progress; var p2 = Math.min(p * p, 1.0); for (var i = 0; i < 256; i++) { var j = i + (50 - i) * p2 | 0; if (j > 255) j = 255; if (j < 0) j = 0; test[i] = j; j = i + (255 - i) * p2 | 0; if (p2 > 0.7) j = 255 * (1.0 - p2) / 0.3 | 0 test2[i] = j; } //    for (var i = 3; i < w * h * 4; i += 4) data[i] = 0; for (var j = hp - 1; j >= 0; j--) for (var i = 0; i < wp; i++, k++) if (this.parts[k] != 0) { //   //   ,        var p = this.progress * this.speed[k]; //      var x0 = i * part; var y0 = j * part; var x = this.px[k], y = this.py[k]; var a = 1.0; //   0.2      if (p > 0.2) { p = (p - 0.2) / 0.8; //   x,          0.1, //          var px = p * this.dir + this.progress * (this.speed[k] * 10 % 0.1); if (this.parts[k] == 2) { //     x = x0 + this.sx + px * w / 2 | 0; y = y0 + p * p * this.h | 0; } else if (this.parts[k] == 3) { //   -,         x = x0 + this.sx + px * w | 0; y = y0 + p * w / 4 | 0; if (this.dir == -1) { a = Math.min(1.0, x / w2); } else if (this.dir == 0) { a = Math.min(1.0, 1.0 - y / h); y = y + p * w / 2 | 0; } else if (this.dir == 1) { a = Math.min(1.0, 2.0 - x / w2); } //   -  if (y + part > h) this.parts[k] = 0; } //    -  if (x < 0 || x + part > w) this.parts[k] = 0; } if (this.parts[k] == 0) continue; var min = 0; //    ,      if (this.parts[k] == 2) { var max = this.level[x] var num = x; //       for (var x1 = x + 1; x1 < x + part; x1++) if (this.level[x1] > max) { num = x1; max = this.level[x1]; } //    if (y + part > max) { y = max - part; x = num; this.level[num]--; this.parts[k] = 1; } } this.px[k] = x; this.py[k] = y; //         ,            if (this.parts[k] == 3 && p > 0.2) { for (var dy = 0; dy < part; dy++) for (var dx = 0; dx < part; dx++) { var s = (x + dx) + (y + dy) * w; var t = (x0 + dx) + (y0 + dy) * w2; s *= 4; t *= 4; data[s] = test[orig[t]]; data[s + 1] = test[orig[t + 1]]; data[s + 2] = test[orig[t + 2]]; data[s + 3] = a * orig[t + 3] | 0; } } else { //      for (var dy = 0; dy < part; dy++) for (var dx = 0; dx < part; dx++) { var s = (x + dx) + (y + dy) * w; var t = (x0 + dx) + (y0 + dy) * w2; s *= 4; t *= 4; data[s] = test[orig[t]]; data[s + 1] = test[orig[t + 1]]; data[s + 2] = test[orig[t + 2]]; data[s + 3] = test2[orig[t + 3]]; } } } //       this.context.putImageData(this.data, 0, 0); } 


The source code with comments can be downloaded here .

Source: https://habr.com/ru/post/153227/


All Articles