【问题标题】:Water ripple effect Python and Pygame, from coding train video水波纹效果 Python 和 Pygame,来自编码火车视频
【发布时间】:2020-06-05 18:25:17
【问题描述】:

我目前正在尝试在 python/pygame(来自 java,p5 来自编码火车视频:https://www.youtube.com/watch?v=BZUdGqeOD0w)中复制水波纹效果。

我无法让它工作,屏幕保持空白(黑色:背景颜色)或者我给了我错误:

Traceback (most recent call last):
  File "/Users/vicki/Documents/Atom Test/Water Ripple.py", line 39, in <module>
    screen.fill((current[i][j],current[i][j],current[i][j]),(i,j,1,1))
TypeError: invalid color argument

实际上我发现这是正常的,因为我们从 0 开始使用正数来获取像素的颜色:

current[i][j] = (previous[i-1][j]+previous[i+1][j]+ previous[i][j+1]+ previous[i][j-1])/ 2 - current[i][j]

previous[i-1][j]previous[i+1][j]previous[i][j-1]previous[i][j+1] 都等于 0,而current[I][j] 是一个正数,所以逻辑上它应该给出一个负颜色值。 也许这与数组在 Python 和 Java 中的工作方式有关?

如需更多帮助,这里是用于制作他的视频的网站编码火车:https://web.archive.org/web/20160418004149/http://freespace.virgin.net/hugo.elias/graphics/x_water.htm

这是我的代码:

import pygame

pygame.init()

width = 200
height = 200

cols = width
rows = height

#dampening = how fast the ripple effect stops.
dampening = 0.999


#Arrays that hold the colors of the screen.
current = [[0]*cols]*rows
previous = [[0]*cols]*rows


screen = pygame.display.set_mode((width,height))
#Sets the initial background to black
screen.fill((0,0,0))

#Mainloop
while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            exit()
        if event.type == pygame.MOUSEBUTTONDOWN:
            #if the user clicks the screen, it sets the pixel the user clicked upon white
            mouse_pos = pygame.mouse.get_pos()
            current[mouse_pos[0]][mouse_pos[1]] = 255



    #This part seems to be the problem
    for i in range(1,rows-1):
        for j in range(1,cols-1):
            #This computation is weird: we are substracting positive/zero number from a null number which should normally give us a negative number. A color value (RGB) cannot be a negative number!
            current[i][j] = (previous[i-1][j]+previous[i+1][j]+ previous[i][j+1]+ previous[i][j-1])/ 2 - current[i][j]
            current[i][j] *= dampening
            #This line is where it gives me the error
            screen.fill((current[i][j],current[i][j],current[i][j]),(i,j,1,1))

    #Switching the arrays, have also tried: previous, current = current, previous (doesn't work either)
    temp = previous
    previous = current
    current = temp
    pygame.display.flip()

这是编码火车的代码(在java P5中): (注意:他使用的是 mousedraft 而不是 mouseTouch(在视频中他使用的是 mousetouch),但这并不重要。)

// Daniel Shiffman
// http://codingtra.in
// http://patreon.com/codingtrain

// 2D Water Ripples
// Video: https://youtu.be/BZUdGqeOD0w
// Algorithm: https://web.archive.org/web/20160418004149/http://freespace.virgin.net/hugo.elias/graphics/x_water.htm

int cols;
int rows;
float[][] current;// = new float[cols][rows];
float[][] previous;// = new float[cols][rows];

float dampening = 0.99;

void setup() {
  size(600, 400);
  cols = width;
  rows = height;
  current = new float[cols][rows];
  previous = new float[cols][rows];
}

void mouseDragged() {
  previous[mouseX][mouseY] = 500;
}

void draw() {
  background(0);

  loadPixels();
  for (int i = 1; i < cols-1; i++) {
    for (int j = 1; j < rows-1; j++) {
      current[i][j] = (
        previous[i-1][j] + 
        previous[i+1][j] +
        previous[i][j-1] + 
        previous[i][j+1]) / 2 -
        current[i][j];
      current[i][j] = current[i][j] * dampening;
      int index = i + j * cols;
      pixels[index] = color(current[i][j]);
    }
  }
  updatePixels();

  float[][] temp = previous;
  previous = current;
  current = temp;
}

【问题讨论】:

标签: python pygame simulation


【解决方案1】:

首先

current = [[0]*cols]*rows
previous = [[0]*cols]*rows

不是二维数组。它生成一个列表,然后生成一个外部列表,其中每个元素都引用同一个内部列表。

如果您想生成一个列表,其中每个元素都是一个单独的列表,那么您必须:
(内部维度应该是行,例如current[col][row]/current[x][y]

current = [[0]*rows for col in range(cols)]
previous = [[0]*rows for col in range(cols)]

在 pygame 中,颜色通道的值必须是 [0, 255] 范围内的整数值:

val = min(255, max(0, round(current[i][j])))
screen.fill((val, val, val), (i,j,1,1)) 

此外,MOUSEBUTTONDOWN 事件仅在按下鼠标按钮时发生一次。你必须评估鼠标的当前状态是否被pygame.mouse.get_pressed()按下:

while True:
    # [...]
    
    if any(pygame.mouse.get_pressed()):
        mouse_pos = pygame.mouse.get_pos()
        previous[mouse_pos[0]][mouse_pos[1]] = 500

我建议分别使用pygame.Surfacepygame.PixelArray 来提高性能。这与Processing 中的loadPixels()/updatePixels() 类似:

img = pygame.Surface((width, height))
while True:
    # [...]

    pixelArray = pygame.PixelArray(img)
    for i in range(1,cols-1):
        for j in range(1,rows-1):
            current[i][j] = (
                previous[i-1][j] + 
                previous[i+1][j] +
                previous[i][j-1] + 
                previous[i][j+1]) / 2 - current[i][j]
            current[i][j] *= dampening
            val = min(255, max(0, round(current[i][j])))
            pixelArray[i, j] = (val, val, val)
    pixelArray.close()
    screen.blit(img, (0, 0))

遗憾的是,一切仍然非常缓慢。完整示例:

import pygame
    
pygame.init()

width = 300
height = 200

cols = width
rows = height

#dampening = how fast the ripple effect stops.
dampening = 0.999

#Arrays that hold the colors of the screen.
current = [[0]*rows for col in range(cols)]
previous = [[0]*rows for col in range(cols)]

print(current[0][0], current[199][199])

screen = pygame.display.set_mode((width,height))
#Sets the initial background to black
screen.fill((0,0,0))

img = pygame.Surface((width, height))

#Mainloop
while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            exit()         

    if any(pygame.mouse.get_pressed()):
        mouse_pos = pygame.mouse.get_pos()
        previous[mouse_pos[0]][mouse_pos[1]] = 500

    #This part seems to be the problem
    pixelArray = pygame.PixelArray(img)
    for i in range(1,cols-1):
        for j in range(1,rows-1):
            current[i][j] = (
                previous[i-1][j] + 
                previous[i+1][j] +
                previous[i][j-1] + 
                previous[i][j+1]) / 2 - current[i][j]
            current[i][j] *= dampening
            val = min(255, max(0, round(current[i][j])))
            pixelArray[i, j] = (val, val, val)
    pixelArray.close()

    # Switching the arrays
    previous, current = current, previous

    screen.blit(img, (0, 0))
    pygame.display.flip()

要获得真正好的性能,您必须使用NumPy。在以下示例中,我使用pygame.time.Clock.tick 限制了每秒帧数:

import numpy
import pygame
import scipy
pygame.init()

window = pygame.display.set_mode((300, 300))
clock = pygame.time.Clock()

size = window.get_size()
dampening = 0.999

current = numpy.zeros(size, numpy.float32)
previous = numpy.zeros(size, numpy.float32)
kernel = numpy.array([[0.0, 0.5, 0], [0.5, 0, 0.5], [0, 0.5, 0]])

run = True
while run:
    clock.tick(60)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False    
        
    if any(pygame.mouse.get_pressed()):
        mouse_pos = pygame.mouse.get_pos()
        previous[mouse_pos] = 1000

    # either:
    # current = (scipy.ndimage.convolve(previous, kernel) - current) * dampening
    # or:
    current[1:size[0]-1, 1:size[1]-1] = (
        (previous[0:size[0]-2, 0:size[1]-2] + 
         previous[2:size[0], 0:size[1]-2] + 
         previous[0:size[0]-2, 2:size[1]] + 
         previous[2:size[0], 2:size[1]]) / 2 - 
        current[1:size[0]-1, 1:size[1]-1]) * dampening

    array = numpy.transpose(255 - numpy.around(numpy.clip(current, 0, 255)))
    array = numpy.repeat(array.reshape(*size, 1).astype('uint8'), 3, axis = 2)
    image = pygame.image.frombuffer(array.flatten(), size, 'RGB')

    previous, current = current, previous

    window.blit(image, (0, 0))
    pygame.display.flip()

pygame.quit()
exit()    

【讨论】:

  • 谢谢它的工作,我想主要问题是我构建数组的方式。 Speedwise 你也是对的:它慢得令人痛苦,我会想办法让它更快。在编码训练视频中,它至少快了 4 倍。我不确定是因为他的电脑还是python和java之间的速度差异。无论如何感谢您解决问题。
  • 如果您有兴趣,我已经找到了一种使程序运行得更快的基本方法。 pygame 版本以 6.5 fps 运行。如果您使用处理(我发现它具有 Python 模式),您可以让代码以 12 fps 的速度运行,这几乎是 2 倍的速度增益。如果您愿意,我可以将处理版本发送给您,即使我认为您自己可以比我做得更好。
猜你喜欢
  • 2011-11-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-06
  • 2014-05-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多