在自定义循环函数中放置P5.js绘制函数

时间:2017-03-01 12:10:20

标签: javascript p5.js

P5.js有自己的循环,每秒调用draw()函数FPS次数。但是,有一个函数noLoop()可以放在setup()函数中,它会禁用P5.js自己的循环。

所以我做了自己的循环(这对我的项目来说是必要的),如下所示:

customLoop = function(){
  while(somethingIsTrue){
    //custom code here
    draw();
  }
}

所以我希望看到画布更新非常快,因为没有FPS设置。但是,仅在customLoop完成后才会重新绘制画布。

简而言之:自定义循环仍在运行时画布不会更新,它只会在自定义循环完成后显示更新。

如何在我自己的循环中连续更新画布?

人们要求更多背景,所以我会尽力解释。我已经制作了own JS library,可以很容易地为神经网络创建遗传算法。

我的库中的遗传算法设置如下:

var evol = new Evolution({
  size: 50,
  mutationRate: 0.05,
  networkSize: [4,10,1],
  mutationMethod: [Mutate.MODIFY_RANDOM_BIAS, Mutate.MODIFY_RANDOM_WEIGHT],
  crossOverMethod: [Crossover.UNIFORM, Crossover.AVERAGE],
  selectionMethod: [Selection.FITNESS_PROPORTIONATE],
  elitism: 5,
  fitnessFunction: function(network){
    //something here that computes for every network, AT THE SAME TIME showing it LIVE for the user to watch
  }
});

注意fitnessFunction中的评论。然后我按如下方式运行遗传算法循环:

 var notFinished = true;
  while(notFinished){
    evol.evaluate();
    if(evol.getAverage() > 2000){
      notFinished = false;
      console.log('done');
    }
    evol.select();
    evol.crossOver();
    evol.mutate();
    evol.replace();
  };

如您所见,我的代码不断运行遗传算法。但整个问题在于健身功能。这种特定的遗传算法用于训练神经网络如何玩。所以在健身功能中,我实际上运行神经网络,直到蛇死亡。但是,对于每个经过测试的蛇,我都希望用户神经网络的运作方式。所以我的健身功能看起来如下:

fitnessFunction = function(network){
  while(snakeIsNotDead){
    computeNeuralNetworkOutput();
    giveSnakeNewMovementDirectionBasedOnOutput();

    // most importantly
    redrawTheSnakeWithNewPosition(); // which is redraw() with P5.js
}

因此,对于经过测试的每一代人,我都想看到所有50只蛇的表现。然而,我在网络完成后看到的只是蛇在最后一次蛇评估中的最后位置。

2 个答案:

答案 0 :(得分:2)

请尝试将您的代码发布为我们实际可以运行的MCVE。这是一个显示问题的小例子:

function setup(){
  createCanvas(500, 500);
  for(var i = 0; i < 600; i ++){
    redraw();
  }
}

function draw(){
  ellipse(random(width), random(height), 20, 20);
}

这将显示一个画布,上面已经绘制了一堆随机圆圈。我知道你想要的是看到圈子累积,就像你快速转发&#34;草图。

使用你自己的循环有点可疑。 Processing已经有了自己的循环机制,它包含用于双缓冲画布的逻辑。这就是为什么在绘图完成之前你没有看到任何东西的原因。您可以依靠Processing的内置计时机制。这是一个例子:

function setup(){
  createCanvas(500, 500);
  frameRate(120);
}

function draw(){
  if(frameCount == 600){
    frameRate(60);
  }
  ellipse(random(width), random(height), 20, 20);

}

请注意,如果您运行的设备限制了帧速率,则可能无法使用此功能。但重点是你不应该创建自己的循环。只需使用Processing的内置循环逻辑。

编辑:就像我说的那样,你没有看到任何帧的原因是因为Processing是双缓冲的。这意味着Processing将等到它通过所有事件并将所有内容绘制到屏幕上。这是一个过于简单化的过程,但您可以将上述循环视为向事件队列添加600个重绘事件。处理通过它们,但在完成之前实际上并未显示结果。

您可以通过在清空此事件队列后调用每次重绘来解决此问题。一种稍微犹豫不决的方法是使用setTimeout()函数:

function setup(){
  createCanvas(500, 500);
  for(var i = 0; i < 600; i ++){
    setTimeout(redraw, 0);
  }
}

function draw(){
  ellipse(random(width), random(height), 20, 20);
}

现在,代码连续600次调用redraw()函数,代码会向事件队列添加600个超时事件。它们中的每一个都将被单独处理,因此您可以看到间歇性帧。

我会说这仍然是实现目标的一种黑客方式。如果我是你,我仍然会尝试将draw()函数与实际逻辑分开。让您的模拟设置变量,并让draw()函数使用这些变量。这是&#34;正确的&#34;解决这个问题的方法。使用setTimeout()功能现在可以使用,但我的猜测是,它会给你带来问题。

答案 1 :(得分:1)

根据文档,您希望避免调用&#34; draw()&#34;直。尝试类似:

customLoop = function(){
    while(somethingIsTrue){
        //custom code here
        redraw();  // Causes the code inside "draw()" to execute once
    }
}

https://p5js.org/reference/#/p5/draw

具体来说: draw()是自动调用的,绝不应该显式调用。 应始终使用noLoop(),redraw()和loop()来控制它。

此外,由于JavaScript是单线程的,只要while循环正在消失,其他任何东西都无法执行。这是一篇相关的文章:

Asynchronous function in a while-loop

一个很好的,彻底的解释:

http://blog.carbonfive.com/2013/10/27/the-javascript-event-loop-explained/

您可以尝试以下方式:

var timerId = 0;

timerId = setTimeout(myCustomRedraw(), 10); // Every 10 milliseconds.
myCustomRedraw = function () {
    // custom code here
    if (<stop loop condition>) {
        clearTimeout(timer); // Will stop the timeout loop.
    }
    redraw()
}