Pat*_*ick 2 html javascript simulation canvas pixel
我正在尝试使用画布在 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);
}Run Code Online (Sandbox Code Playgroud)
<!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>Run Code Online (Sandbox Code Playgroud)
希望您能够帮助我。
有很多选项可以加速您的代码
以下内容会使大多数工作量太大的机器陷入困境。
// 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);
}
}
Run Code Online (Sandbox Code Playgroud)
不要通过矩形写入每个像素。使用可以通过createImageData和相关函数从画布 API 获取的像素数据。它使用比数组快一点的类型化数组,并且可以对同一内容有多个视图。
您可以在一次调用中将所有像素写入画布。不是非常快,而是比你正在做的事情快上亿倍。
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
Run Code Online (Sandbox Code Playgroud)
在像素坐标处获取像素
const pixel = data32[x + y * imageData.width];
Run Code Online (Sandbox Code Playgroud)
像素数据在您将其放到画布上之前不会显示
ctx.putImageData(imageData,0,0);
Run Code Online (Sandbox Code Playgroud)
这会给你带来很大的进步。
当性能至关重要时,您会牺牲内存和简单性来获得更多 CPU 周期做您想做的事,而减少无所事事。
您将红色像素随机扩展到场景中,读取每个像素并检查(通过慢速字符串比较)是否为红色。当你找到一个时,你会在它旁边添加一个随机的红色像素。
检查绿色像素是一种浪费,可以避免。扩展完全被其他红色包围的红色像素也是没有意义的。他们什么都不做。
您唯一感兴趣的像素是绿色像素旁边的红色像素。
因此,您可以创建一个缓冲区来保存所有活动红色像素的位置,活动红色至少有一个绿色。每一帧你都会检查所有活跃的红色,如果可以的话产生新的,如果它们被红色包围,就杀死它们。
我们不需要存储每个红色的 x,y 坐标,只需要存储内存地址,这样我们就可以使用平面数组。
const reds = new Uint32Array(size * size); // max size way over kill but you may need it some time.
Run Code Online (Sandbox Code Playgroud)
您不想在 reds 数组中搜索红色,因此您需要跟踪有多少活跃的红色。您希望所有活动的红色都位于数组的底部。每帧您只需要检查每个活动的红色一次。如果一个红色比上面所有的都死了,它必须向下移动一个数组索引。但是您只想每帧只移动每个红色一次。
我不知道这种阵列叫什么,它就像一个分离罐,死的东西慢慢上移,活的东西下移。或者未使用的物品会冒泡,使用过的物品会沉到底部。
我将它显示为函数式,因为它更容易理解。但更好地实现为一种蛮力功能
// 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;
}
Run Code Online (Sandbox Code Playgroud)
现在处理所有的红色。
这是气泡阵列的核心。head用于索引每个活动的红色。tail是在head没有遇到死亡的情况下将电流移动到哪里的索引tail等于head。然而,如果遇到一个死项目,则head向上移动一个,而tail剩余部分则指向死项目。这会将所有活动项目移动到底部。
当head === count所有活动项目都被选中时。tail现在的值包含count迭代后设置的新值。
如果您使用的是对象而不是整数,则不是向下移动活动项,而是交换head和tail项。这有效地创建了一个可用对象池,可在添加新项目时使用。这种类型的数组管理不会产生 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;
}
Run Code Online (Sandbox Code Playgroud)
该演示将向您展示速度改进。我使用了功能版本,可能还有其他一些调整。
您还可以考虑使用webWorkers来提高事件速度。Web Worker 在单独的 javascript 上下文中运行并提供真正的并发处理。
为了获得终极速度,请使用 WebGL。所有逻辑都可以通过 GPU 上的片段着色器完成。这种类型的任务非常适合 GPU 设计用于的并行处理。
稍后会回来清理这个答案(有点太长了)
我还为像素阵列添加了一个边界,因为红色从像素阵列中产生。
// 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);
}
}
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)