Javascript HTML5画布,更好的像素控制和更快的速度
我试图在HTML5和Javascript的帮助下使用画布创建一个小的模拟。然而,我的问题是,我真的想不出一种方法来控制我的像素的行为,而不使每个像素都成为一个对象,这会导致我的模拟速度大大减慢 以下是迄今为止的代码:Javascript HTML5画布,更好的像素控制和更快的速度,javascript,html,canvas,simulation,pixel,Javascript,Html,Canvas,Simulation,Pixel,我试图在HTML5和Javascript的帮助下使用画布创建一个小的模拟。然而,我的问题是,我真的想不出一种方法来控制我的像素的行为,而不使每个像素都成为一个对象,这会导致我的模拟速度大大减慢 以下是迄今为止的代码: var像素=[]; 类像素{ 构造器(颜色){ 这个。颜色=颜色; } } window.onload=function(){ canv=document.getElementById(“canv”); ctx=canv.getContext(“2d”); createMap()
var像素=[];
类像素{
构造器(颜色){
这个。颜色=颜色;
}
}
window.onload=function(){
canv=document.getElementById(“canv”);
ctx=canv.getContext(“2d”);
createMap();
设定间隔(游戏,1000/60);
};
函数createMap(){
像素=[];
对于(i=0;i而言,您的速度减慢的主要原因是您假设每次操作都需要在每个像素上循环。您不需要这样做,因为每次需要执行的操作都需要640000次迭代
您也不应该在渲染循环中执行任何操作逻辑。唯一应该存在的是绘图代码。因此,最好将其移出到单独的线程(Web Workers)。如果无法使用这些线程,请调用setTimeout/Interval
因此,首先要做几个小改动:
使像素类包含像素的坐标和颜色:
class Pixel{
constructor(color,x,y){
this.color=color;
this.x = x;
this.y = y;
}
}
保留一个像素数组,该数组将创建新的红色像素。另一个用于跟踪哪些像素已更新,以便我们知道哪些像素需要绘制
var pixels = [];
var infectedPixesl = [];
var updatedPixels = [];
现在代码中最容易更改的部分是渲染循环,因为它只需要绘制像素,所以只需要几行
function render(){
var numUpdatedPixels = updatedPixels.length;
for(let i=0; i<numUpdatedPixels; i++){
let pixel = updatedPixels[i];
ctx.fillStyle = pixel.color;
ctx.fillRect(pixel.x,pixel.y,1,1);
}
//clear out the updatedPixels as they should no longer be considered updated.
updatedPixels = [];
//better method than setTimeout/Interval for drawing
requestAnimationFrame(render);
}
演示
var像素=[],
infectedPixels=[],
updatedPixels=[],
canv,ctx;
window.onload=函数(){
canv=document.getElementById(“canv”);
ctx=canv.getContext(“2d”);
createMap();
render();
设置间隔(()=>{
更新();
}, 16);
};
函数createMap(){
for(设y=0;y<800;y++){
像素。推送([]);
对于(x=0;x<800;x++){
像素[y]。推送(新像素(“绿色”,x,y));
}
}
像素[400][400]。颜色=“红色”;
updatedPixels=[].concat(…像素);
push(像素[400][400]);
}
类像素{
构造函数(颜色、x、y){
这个颜色=颜色;
这个.x=x;
这个。y=y;
}
}
函数更新(){
var affectedPixels=[];
var stillInfectedPixels=[];
var numinInfected=infectedPixels.length;
for(设i=0;ip);
var rand=Math.floor((Math.random()*surroundingPixels.length));
让selectedPixel=环绕像素[rand];
如果(selectedPixel.color==“绿色”){
selectedPixel.color=“红色”;
影响像素。按(选定像素);
}
if(!surroundingPixels.every(p=>p.color==“red”)){
仍然感染像素。按(像素);
}
}
infectedPixels=stillInfectedPixels.concat(infectedPixels);
updatedPixels.push(…影响像素);
}
函数render(){
var numUpdatedPixels=updatedPixels.length;
for(设i=0;i
优化像素操作
有许多选项可以加快代码的速度
像素为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);
}
}
以像素坐标获取像素
const pixel = data32[x + y * imageData.width];
有关使用图像数据的详细信息,请参见
像素数据只有放在画布上才会显示
ctx.putImageData(imageData,0,0);
这会给你带来很大的进步
更好的数据组织。
当性能至关重要时,您会牺牲内存和简单性,以获得更多的CPU周期来做您想做的事情,而不是什么都不做
红色像素随机扩展到场景中,读取每个像素并检查(通过慢速字符串比较)是否为红色。当找到一个像素时,在其旁边添加一个随机的红色像素
检查绿色像素是一种浪费,是可以避免的。扩展完全被其他红色包围的红色像素也是毫无意义的。它们什么都不做
您感兴趣的唯一像素是绿色像素旁边的红色像素
因此,您可以创建一个缓冲区来保存所有活动红色像素的位置,活动红色至少有一个绿色。每一帧您检查所有活动红色,如果可以,生成新的红色,如果它们被红色包围,则杀死它们
我们不需要存储每个红色的x,y坐标,只需要存储地址,这样我们就可以使用平面阵列
const reds = new Uint32Array(size * size); // max size way over kill but you may need it some time.
你不想在你的红色数组中搜索红色,所以你需要记录有多少个活动的红色。你希望所有活动的红色都位于数组的底部。你需要每帧只检查一次每个活动的红色。如果一个红色比上面所有的红色都死了,它必须向下移动一个数组索引。但是你只想只移动每个红色每帧一次
气泡阵列
我不知道这种阵列叫什么,它就像一个分离罐,死的东西慢慢向上移动,活的东西向下移动。或者不用的东西冒出气泡,用过的东西沉入底部
我将把它显示为功能性的,因为它更容易理解。但是作为一个蛮力函数更好地实现
// 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;
}
演示。
演示将向您展示速度的提高。我使用了功能版本,可能还有其他一些调整
您也可以考虑让事件更快速。Web工作在单独的JavaScript上下文中运行,并提供真正的并发处理。
为了达到最高速度,请使用WebGL。所有的逻辑都可以通过GPU上的片段着色器来完成。这种类型的任务非常好
const reds = new Uint32Array(size * size); // max size way over kill but you may need it some time.
// 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;
}
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;
}