将 P5.js 绘制函数放置在自定义循环函数中
P5.js 有自己的循环,每秒调用
draw()
函数
FPS
次。但是,有一个函数
noLoop()
可以放在
setup()
函数中,它禁用 P5.js 自己的循环。
所以我制作了自己的循环(这对我的项目来说是必要的),如下所示:
customLoop = function(){
while(somethingIsTrue){
//custom code here
draw();
}
}
因此,由于没有设置 FPS,我希望看到画布更新得相当快。但是,只有在
customLoop
完成后,画布才会再次重新绘制。
简而言之:自定义循环仍在运行时,画布不会更新,它只会在自定义循环完成后显示更新。
如何在自己的循环中连续更新画布?
人们要求提供更多背景信息,所以我会尽力解释。我已经制作了 自己的 JS 库 ,可以轻松创建神经网络的遗传算法。
我的库中的遗传算法设置如下:
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 条蛇的表现。但是,在网络完成后,我看到的只是蛇在最后一次蛇评估中的最后位置。
请尝试将您的代码发布为我们可以实际运行的 MCVE 。下面是一个显示您的问题的小示例:
function setup(){
createCanvas(500, 500);
for(var i = 0; i < 600; i ++){
redraw();
}
}
function draw(){
ellipse(random(width), random(height), 20, 20);
}
这将显示一个画布,上面已经绘制了一堆随机圆圈。我猜您想要的是看到圆圈累积起来,就像您正在“快进”草图一样。
使用您自己的循环有点可疑。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 个重绘事件。Processing 会处理它们,但直到完成才会真正显示结果。
您可以通过在此事件队列清空后调用每个重绘来解决这个问题。一种略显 hack 的方法是使用
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);
}
现在,代码不再连续调用
redraw()
函数 600 次,而是向事件队列添加 600 个超时事件。它们中的每一个都将单独处理,因此您会看到间歇性帧。
我要说的是,这仍然似乎是一种实现目标的黑客方法。如果我是你,我仍然会尝试将
draw()
函数与实际逻辑分开。让您的模拟设置变量,并让
draw()
函数使用这些变量。这是解决这个问题的“正确”方法。使用
setTimeout()
函数目前可以工作,但我猜它会在以后给您带来问题。
根据文档,您需要避免直接调用“draw()”。请尝试以下方法:
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 循环在运行,其他任何操作都无法执行。以下是相关文章:
以及一个详尽的解释:
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()
}