python - 水波纹效果 Python 和 Pygame,来自编码火车视频

标签 python pygame simulation

我目前正在尝试在 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]

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

如需更多帮助,请访问coding train 用于制作视频的网站: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;
}

最佳答案

首先

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 中,颜色 channel 的值必须是 [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.Surface分别pygame.PixelArray以提高性能。类似loadPixels()/updatePixels()Processing :

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()    

关于python - 水波纹效果 Python 和 Pygame,来自编码火车视频,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60336688/

相关文章:

macos - 导入 pygame.font 失败

python - 我正在将 pygame 窗口嵌入到 Tkinter 中,如何操作 pygame 窗口?

java - 实现模拟线程#sleep()

python - 为什么 base64.b64encode() 返回一个字节对象?

python - django的View类是如何工作的

Python - Discord.py - wait_for() 我的 Cog 的意外关键字

c++ - 如何获得特定范围内的汽车数量

python - 在 Windows 上安装 scipy 的问题

python - 如何在pygame中等待一段时间?

java - 处理时间的 Anylogic 移动平均值