优化 p5.js 项目/沙盒/像素模拟器。

5
我开始进行一个像素模拟项目,但运行效果不佳。我该如何优化它?我相当自信问题出在涉及粒子数组的所有检查中。我正在寻找任何解决方案,以做出任何不同的事情或整体优化,不仅限于粒子数组,而是整个代码。我的目标是创建一个像素模拟 Web 应用程序,如果在生成沙子几秒钟后不能良好运行,那就失去了 Web 应用程序的意义。感谢任何帮助。
主文件:

let alter = true;

let particles = []

function setup() {
  let canvas = createCanvas(windowWidth, windowHeight);
  frameRate(120);
}

var a = ['rgb(244,220,148)', 'rgb(236,211,140)', 'rgb(252,228,156)', 'rgb(252,220,149)', 'rgb(244,212,148)', 'rgb(228,204,132)', 'rgb(240,220,156)']

function sandColor() {
  return color(a[Math.floor(Math.random() * a.length)]);
}

function drect(c, x, y, l, w) {
  noStroke();
  fill(c);
  rect(x, y, l, w);
}

class Particle {
  constructor(p, c, x, y, s) {
    this.p = p;
    this.c = c;
    this.x = x;
    this.y = y;
    this.s = s;
  }

  draw() {
    drect(this.c, this.x, this.y, this.s, this.s);
  }
}

function check(x, y) {
  return color(get(x, y));
}

function draw() {

  drect(color(37, 150, 190), 0, 0, windowWidth, windowHeight)

  tw = 4;
  th = 4;

  for (let i = 0; i < particles.length; i++) {
    particles[i].draw()
  }

  alter = !(alter)
  if (!alter) {

    for (let i = 0; i < particles.length; i++) {
      if (particles[i].p == 's') {
        let down = false
        if (JSON.stringify(check(particles[i].x, particles[i].y + 4).levels) == '[37,150,190,255]') {
          particles[i].y += 4;
          down = true;
        }
        if (!down) {
          let r = Math.floor(Math.random() * 2);
          if (r == 0) {
            if (JSON.stringify(check(particles[i].x - 4, particles[i].y + 4).levels) == '[37,150,190,255]') {
              particles[i].y += 4;
              particles[i].x -= 4;
            } else {
              if (JSON.stringify(check(particles[i].x + 4, particles[i].y + 4).levels) == '[37,150,190,255]') {
                particles[i].y += 4;
                particles[i].x += 4;
              }
            }
          }
        }
      }
    }

    if (mouseIsPressed) {
      for (let i = 0; i < 6; i++) {
        for (let j = 0; j < 6; j++) {
          let p = 's'
          let c = sandColor()
          let x = (Math.floor(mouseX / tw)) * tw + (i * 4) - 9;
          let y = (Math.floor(mouseY / th)) * th + (j * 4) - 9;
          let s = 4;

          let sand = new Particle(p, c, x, y, s)
          let d = true;
          for (let m = 0; m < particles.length; m++) {
            if (particles[m].x == x && particles[m].y == y && particles[m].p == "s") {
              d = false;
            }
          }
          if (d) {
            drect(c, x, y, s, s)
            particles.push(sand)
          }
        }
      }
    }
  }
}

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
}

document.addEventListener('contextmenu', event => event.preventDefault());
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.2/p5.min.js"></script>

托管示例:https://pixsim.loganstottle202.repl.co/,如果无法使用,则代码在此处:https://replit.com/@LoganStottle202/pixsim?v=1


1
似乎是与稍微不同的账户名重复发布了 像素/沙盒模拟器优化 的帖子。 - ggorlen
2个回答

2

很遗憾我没有时间提供像你有趣的草图一样漂亮详细的可运行示例。

但我可以给你指几个方向:

  • 正如p5 get() 的文献所述:“使用 get(x,y)获取单个像素的颜色很容易,但不如直接从 pixels[] 抓取数据快。”。您可以在帧的开始调用loadPixels(),然后使用pixels[4 * (x * d + i) + ((y * d + j) * width * d + )],其中d = pixelDensity()
  • 对于许多粒子,JSON.stringify()和比较字符串可能会变得计算密集。考虑将粒子的状态(碰撞、移动、静止/不活跃等)标记为整数值,您可以直接进行比较(例如再次使用具有描述性名称的const整数状态)。 (您可以重复使用此状态以在不同颜色中呈现粒子)。如果您真的想使用颜色,那么您可以通过使用alpha值来简化不同状态(例如255、254、253等),其中感知上很难注意到,并且您可以只与.levels[3]进行比较,避免使用JSON.stringify()和字符串比较。我建议使用具有描述性名称的全局或静态Particle状态const变量和基本的state属性来处理Particle实例。
  • 您可以使用createGraphics()将图形分为两个“层”:一个用于呈现“active/alive”活粒子(它甚至可以是全局p5图像缓冲区)。另一层可用于呈现已定居的静态粒子。然后,您可以使用image(yourStaticSandLayer, 0, 0);重新使用/重绘此图层,从而消除静态粒子的rect()调用(因为它们已经被绘制/缓存到图层中了)。

(此外,由于p5的random()也可以为您选择数组中的随机项,因此您可以将sandColor()简化为return random(a);。这不会加速任何内容,只会简化代码:使其更易于阅读/维护)

更新 这是基于您的Particle类和使用p5.Graphics的基本演示:

let particles = [];
let activeParticlesLayer;
let inactiveParticlesLayer;

function setup() {
  createCanvas(300, 150);
  
  activeParticlesLayer = createGraphics(width, height);
  inactiveParticlesLayer = createGraphics(width, height);
  
  for(let i = 0;  i < 10; i++){
    particles.push(new Particle(color(0, 192, 0), random(width), random(90), 10, 10));
  }
}

function draw() {
  // clear only active layer: don't clear inactive layer
  activeParticlesLayer.background(255);
  
  // for debugging only: count active particles
  let numActiveParticles = 0;
  for(let i = 0;  i < 10; i++){
    let p = particles[i];
    // only update particles if they're active
    if(p.isActive){
      // if the current particle collided (with stage bottom for now)
      // then make it inactive (change colour as a visual cue) and cache to inactive layer
      // replace this with pixels[] collision logic
      if(p.y > height - p.s){
        p.isActive = false;
        p.c = color(0, 128, 0);
        // particle is inactive: cache into inactive layer (which isn't cleared)
        p.draw(inactiveParticlesLayer); 
      }
      // otherwise our particle is active: update position and render to active layer
      else{
        p.y += 1;
        p.draw(activeParticlesLayer);
        numActiveParticles++;
      }  
    }
  }
  
  // display layers
  image(activeParticlesLayer, 0, 0);
  image(inactiveParticlesLayer, 0, 0);
  text("active particles: " + numActiveParticles, 10, 15);
}

class Particle {
  constructor(c, x, y, s) {
    this.isActive = true;
    this.c = c;
    this.x = x;
    this.y = y;
    this.s = s;
  }
  
  update(){
    if(this.y > height - this.s){
      this.isActive = false;
      this.c = color(0, 128, 0);
    }else{
      this.y += 1;  
    }
  }

  draw(buffer) {
    buffer.fill(this.c);
    buffer.rect(this.x, this.y, this.s, this.s);
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.2/p5.min.js"></script>

注意:上面的代码删除了你原始演示中的一些功能,例如使用颜色检查碰撞。为了简单起见,只有很少的粒子与画布底部碰撞(不堆叠)。希望这足以说明“isActive”状态的变化和仅在状态从活动到非活动时缓存“rect()”调用一次。
此外,我建议查看开发人员工具,并进行代码分析,以确定哪些代码比较慢,并专注于那些代码(而不是优化对代码影响小或没有影响但可能使代码在将来难以维护/难以阅读的代码)。

感谢您的帮助,我已经实现了像素pixels[]。 我很好奇createGraphics()和particle states。 如果可以的话,您能否在Discord上向我发送一些示例代码或告诉我如何将其实现到我的项目中?我的Discord是bread#7194。 - user19897564
@breaad_2,我刚刚发布了一个更新,其中包括了一个有注释的示例。希望这能说明问题。(对不起,今天 Discord 上的时间不多了)。 - George Profenza
请注意,上述代码并没有将粒子数组拆分为两个数组(活动/非活动),而是在两者之间来回移动(尽管这可能对您的用例有用或无用)。上述代码主要是在非活动粒子从活动状态变为非活动状态时呈现静止画面。循环仍然遍历所有粒子,并且目前仅更新活动粒子。当然,您可以根据自己的逻辑扩展代码以更新非活动粒子,以潜在地将其状态从非活动重置为活动... - George Profenza
如果您不想这样做,那完全没问题,您能否给我发送一个关于这种工作方式的资源链接或者告诉我更多关于我的具体代码案例以及如何实现它?再次感谢您的所有帮助。 :) - user19897564
1
谢谢,我会仔细阅读并尝试理解它。此外,我通常会努力确保理解我使用的代码,这就是为什么在将该系统放入我的项目之前向您提出疑问的原因。祝您有美好的一天! - user19897564
显示剩余5条评论

0

我昨天看到你的GitHub链接,并创建了一个PR来实现一些小的性能改进。

这些改进大多是小的。我认为你需要改成基于像素的系统来进行性能全面升级,但如果你坚持这种方法,这些改进应该会有所帮助。

  1. 移除重复声明。

你在draw()循环内部声明了常量twth,每个周期都不必要地重新声明。我将它们放在draw()函数外部。

  1. 修改循环逻辑

你有两个for (i in particles)循环。我将逻辑放在一个循环内,这样你只需要循环一半的次数。此外,我将for...in循环改为for循环以提高性能。

  1. 移除无效粒子

我发现只有添加粒子的代码,没有删除粒子的代码。我添加了一个检查,如果粒子已经掉落到windowHeight以下,则从particles数组中移除它们,以防止数组无限增长。

希望这能有所帮助。

总的来说,我认为George提供了很好的见解,可以进一步重构和改善您的代码。希望这能帮助您创建一个有趣的项目。我很想看到更多!


1
谢谢你的见解。你提到的很多事情已经被改变或彻底改革了,我会保持replit的更新,所以当前的代码在那里。我会查看拉取请求上的改进。我不会经常检查这个,所以如果你有更多的改进意见,可以在discord上给我发消息,我的账号是bread#7194。谢谢! - user19897564

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接