javascript - HTML5 Canvas,更好的像素控制和更快的速度

标签 javascript html canvas simulation pixel

我正在尝试使用 Canvas 在HTML5和Javascript的帮助下创建一些模拟。但是,我的问题是,我无法真正想到一种方法来控制像素的行为,而不能将每个像素都设为一个对象,这会导致仿真速度大大降低。

到目前为止的代码:

var pixels = [];
class Pixel{
    constructor(color){
        this.color=color;
    }
}

window.onload=function(){
    canv = document.getElementById("canv");
    ctx = canv.getContext("2d");
    createMap();
    setInterval(game,1000/60);
};

function createMap(){
    pixels=[];
    for(i = 0; i <= 800; i++){
        pixels.push(sub_pixels = []);
        for(j = 0; j <= 800; j++){
            pixels[i].push(new Pixel("green"));
        }
    }
    pixels[400][400].color="red";
}

function game(){
    ctx.fillStyle = "white";
    ctx.fillRect(0,0,canv.width,canv.height);
    for(i = 0; i <= 800; i++){
        for(j = 0; j <= 800; j++){
            ctx.fillStyle=pixels[i][j].color;
            ctx.fillRect(i,j,1,1);
        }
    }
    for(i = 0; i <= 800; i++){
        for(j = 0; j <= 800; j++){
            if(pixels[i][j].color == "red"){
                direction = Math.floor((Math.random() * 4) + 1);
                switch(direction){
                    case 1:
                        pixels[i][j-1].color= "red";
                        break;
                    case 2:
                        pixels[i+1][j].color= "red";
                        break;
                    case 3:
                        pixels[i][j+1].color= "red";
                        break;
                    case 4:
                        pixels[i-1][j].color= "red";
                        break;
                }
            }
        }
    }

}

function retPos(){
    return Math.floor((Math.random() * 800) + 1);
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <script language="javascript" type="text/javascript" src="game.js"></script>
</head>
<body>
    <canvas width="800px" height="800px" id="canv"></canvas>
</body>

</html>

所以我的两个大问题是,有什么更好的方法控制这些像素?我如何加快像素生成速度?

希望您能够帮助我。

最佳答案

优化像素操纵

有很多选项可以加快代码速度

像素为32位整数

以下内容将使大多数机器负担过多的工作。

    // I removed fixed 800 and replaced with const size 
    for(i = 0; i <= size; i++){
        for(j = 0; j <= size; j++){
            ctx.fillStyle=pixels[i][j].color;
            ctx.fillRect(i,j,1,1);
        }
    }

不要通过rect写入每个像素。使用可以通过createImageData和关联函数从canvas API获取的像素数据。它使用类型化数组,该类型数组比数组快一点,并且可以在同一内容上具有多个 View 。

您可以在一次调用中将所有像素写入 Canvas 。速度不是令人眼花but乱,但比您正在做的速度快十亿倍。
 const size = 800;
 const imageData = ctx.createImageData(size,size);
 // get a 32 bit view
 const data32 = new Uint32Array(imageData.data.buffer);

 // To set a single pixel
 data32[x+y*size] = 0xFF0000FF;  // set pixel to red

 // to set all pixels 
 data32.fill(0xFF00FF00);  // set all to green

在像素坐标处获取像素
 const pixel = data32[x + y * imageData.width];

有关使用图像数据的更多信息,请参见Accessing pixel data

像素数据只有在将其放到 Canvas 上后才会显示
 ctx.putImageData(imageData,0,0);

那会给你带来很大的进步。

更好的数据组织。

当性能至关重要时,您会牺牲内存和简单性来获得更多的CPU周期来完成您想要的事情,而少做一些事情。

您有红色像素随机扩展到场景中,读取每个像素并检查(通过慢速字符串比较)它是否为红色。当您找到一个像素时,您会在其旁边添加一个随机的红色像素。

检查绿色像素是一种浪费,可以避免。扩展被其他红色完全包围的红色像素也是没有意义的。他们什么都不做。

您感兴趣的唯一像素是绿色像素旁边的红色像素。

因此,您可以创建一个缓冲区来保存所有 Activity 红色像素的位置。 Activity 红色至少具有一个绿色。每帧都要检查所有 Activity 的红色,如果可以,则生成新的红色,如果周围有红色,则将其杀死。

我们不需要存储每个红色的x,y坐标,只需存储内存地址,因此我们可以使用平面数组。
const reds = new Uint32Array(size * size); // max size way over kill but you may need it some time.

您不需要在红色数组中搜索红色,因此您需要跟踪有多少个 Activity 红色。您希望所有 Activity 的红色都位于阵列的底部。您只需要每帧检查一次每个 Activity 的红色。如果一个红色比所有红色都死了,则必须向下移动一个数组索引。但是,您只想每帧只移动一次红色。

气泡阵列

我不知道这种类型的阵列像分离 jar 一样,死物缓慢向上移动,而活物向下移动。或未使用的物品冒起,用过的物品落到底部。

我将其显示为功能性的,因为它更易于理解。但更好地实现为一种蛮力功能
// data32 is the pixel data
const size = 800; // width and height
const red = 0xFF0000FF; // value of a red pixel
const green = 0xFF00FF00; // value of a green pixel
const reds = new Uint32Array(size * size); // max size way over kill but you     var count = 0; // total active reds
var head = 0; // index of current red we are processing
var tail = 0; // after a red has been process it is move to the tail
var arrayOfSpawnS = [] // for each neighbor that is green you want
                      // to select randomly to spawn to. You dont want
                      // to spend time processing so this is a lookup 
                      // that has all the possible neighbor combinations
for(let i = 0; i < 16; i ++){
      let j = 0;
      const combo = [];
      i & 1 && (combo[j++] = 1);  // right
      i & 2 && (combo[j++] = -1);  // left
      i & 4 && (combo[j++] = -size); // top
      i & 5 && (combo[j++] = size);  // bottom
      arrayOfSpawnS.push(combo);
}


function addARed(x,y){   // add a new red
     const pixelIndex = x + y * size;
     if(data32[pixelIndex] === green) { // check if the red can go there
         reds[count++] = pixelIndex;  // add the red with the pixel index
         data32[pixelIndex] = red; // and set the pixel
     }
}
function safeAddRed(pixelIndex) { // you know that some reds are safe at the new pos so a little bit faster 
     reds[count++] = pixelIndex;  // add the red with the pixel index
     data32[pixelIndex] = red; // and set the pixel
}

// a frame in the life of a red. Returns false if red is dead
function processARed(indexOfRed) {
     // get the pixel index 
     var pixelIndex = reds[indexOfRed];
     // check reds neighbors right left top and bottom
     // we fill a bit value with each bit on if there is a green
     var n = data32[pixelIndex + 1] === green ? 1 : 0;
     n += data32[pixelIndex - 1] === green ? 2 : 0;          
     n += data32[pixelIndex - size] === green ? 4 : 0;
     n += data32[pixelIndex + size] === green ? 8 : 0;

     if(n === 0){ // no room to spawn so die
          return false;
     }
     // has room to spawn so pick a random
     var nCount = arrayOfSpawnS[n].length;
     // if only one spawn point then rather than spawn we move
     // this red to the new pos.
     if(nCount === 1){
          reds[indexOfRed] += arrayOfSpawnS[n][0]; // move to next pos
     }else{  // there are several spawn points
          safeAddRed(pixelIndex + arrayOfSpawnS[n][(Math.random() * nCount)|0]);
      }
     // reds frame is done so return still alive to spawn another frame
     return true;
  }

现在处理所有红色。

这是气泡阵列的核心。 head用于索引每个 Activity 的红色。 tail是在没有死亡的情况下将当前head移至何处的索引tail等于head。但是,如果遇到了死物,则head会向上移动一个,而tail仍指向死物。这会将所有 Activity 项目移到底部。

head === count时,所有 Activity 项均已检查。现在,tail的值包含新的count,该值在迭代后设置。

如果您使用的是对象而不是整数,则不交换 Activity 项目,而是交换headtail项目。这有效地创建了可用对象池,可在添加新项目时使用它们。这种类型的阵列管理不会产生GC或分配开销,因此与堆栈和对象池相比非常快。
   function doAllReds(){
       head = tail = 0; // start at the bottom
       while(head < count){
            if(processARed(head)){ // is red not dead
                reds[tail++] = reds[head++];  // move red down to the tail
             }else{ // red is dead so this creates a gap in the array  
                   // Move the head up but dont move the tail, 
                    // The tail is only for alive reds
                head++;
            }
       }
       // All reads done. The tail is now the new count
       count = tail;
    }

演示。

该演示将向您展示速度的提高。我使用的是功能版本,可能还会有其他一些调整。

您还可以考虑使用webWorkers来提高事件的速度。 Web Worker在单独的javascript上下文上运行,并提供真正的并发处理。

为了达到最终速度,请使用WebGL。所有逻辑都可以通过GPU上的片段着色器完成。这类任务非常适合针对GPU设计的并行处理。

稍后会回来整理此答案(时间过长)

当红色从像素阵列中产生时,我还为像素阵列添加了边界。

const size = canvas.width;
canvas.height = canvas.width;
const ctx = canvas.getContext("2d");
const red = 0xFF0000FF;
const green = 0xFF00FF00;
const reds = new Uint32Array(size * size);    
const wall = 0xFF000000;
var count = 0;
var head = 0;
var tail = 0;
var arrayOfSpawnS = []
for(let i = 0; i < 16; i ++){
      let j = 0;
      const combo = [];
      i & 1 && (combo[j++] = 1);  // right
      i & 2 && (combo[j++] = -1);  // left
      i & 4 && (combo[j++] = -size); // top
      i & 5 && (combo[j++] = size);  // bottom
      arrayOfSpawnS.push(combo);
}
const imageData = ctx.createImageData(size,size);
const data32 = new Uint32Array(imageData.data.buffer);
function createWall(){//need to keep the reds walled up so they dont run free
    for(let j = 0; j < size; j ++){
         data32[j] = wall;
         data32[j * size] = wall;
         data32[j * size + size - 1] = wall;
         data32[size * (size - 1) +j] = wall;
     }
}
function addARed(x,y){  
     const pixelIndex = x + y * size;
     if (data32[pixelIndex] === green) { 
         reds[count++] = pixelIndex;  
         data32[pixelIndex] = red; 
     }
}
function processARed(indexOfRed) {
     var pixelIndex = reds[indexOfRed];
     var n = data32[pixelIndex + 1] === green ? 1 : 0;
     n += data32[pixelIndex - 1] === green ? 2 : 0;          
     n += data32[pixelIndex - size] === green ? 4 : 0;
     n += data32[pixelIndex + size] === green ? 8 : 0;
     if(n === 0) {  return false }
     var nCount = arrayOfSpawnS[n].length;
     if (nCount === 1) { reds[indexOfRed] += arrayOfSpawnS[n][0] }
     else { 
         pixelIndex += arrayOfSpawnS[n][(Math.random() * nCount)|0]
         reds[count++] = pixelIndex; 
         data32[pixelIndex] = red; 
     }
     return true;
}

function doAllReds(){
   head = tail = 0; 
   while(head < count) {
        if(processARed(head)) { reds[tail++] = reds[head++] }
        else { head++ }
   }
   count = tail;
}
function start(){
	data32.fill(green);  
  createWall();
    var startRedCount = (Math.random() * 5 + 1) | 0;
    for(let i = 0; i < startRedCount; i ++) { addARed((Math.random() * size-2+1) | 0, (Math.random() * size-2+1) | 0) }
    ctx.putImageData(imageData,0,0);
    setTimeout(doItTillAllDead,1000);
    countSameCount = 0;
}
var countSameCount;
var lastCount;
function doItTillAllDead(){
    doAllReds();	
    ctx.putImageData(imageData,0,0);
	if(count === 0 || countSameCount === 100){ // all dead
	    setTimeout(start,1000);
	}else{
        countSameCount += count === lastCount ? 1 : 0;
        lastCount = count; // 

        requestAnimationFrame(doItTillAllDead);
	}
}
start();
<canvas width="800" height="800" id="canvas"></canvas>

关于javascript - HTML5 Canvas,更好的像素控制和更快的速度,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46017660/

相关文章:

javascript - jQuery 版本冲突 - 同时加载两个版本

javascript - 如何阻止在 Google Blogger 模板中加载 CSS 和 js 文件?

python - 使用 python Selenium 获取动态生成的内容

jquery - 如何在类型为 ="range"的 CANVAS 中更改颜色?

javascript - 检测 HTML 元素从顶部到左侧的距离?

javascript - 按下按钮时无法使用 jquery 打印字符串

javascript - MultipartFile 数组未发送到 spring Controller

JavaScript getElementById 不工作

html - 垂直和水平对齐 div 内的 div 与 :after selector

Java:重新定位绘制到 JFrame 中的 Canvas 时出现问题