Pygame 水物理未按预期工作
Pygame water physics not working as intended
所以,我一直在尝试根据本教程pygame实现水物理
问题是,当我在 pygame 中实现此代码时,水决定它不会产生很好的涟漪效应,而是会疯狂地在屏幕上晃动,直到最终崩溃。
我在一些不和谐的服务器上四处寻找,发现其他人已经尝试实现同样的事情,但遇到了同样的问题。他们的代码基本上和我的一样,只是组织得更整齐,所以我会 post 它而不是我的。
import pygame, random
import math as m
from pygame import *
pygame.init()
WINDOW_SIZE = (854, 480)
screen = pygame.display.set_mode(WINDOW_SIZE,0,32) # initiate the window
clock = pygame.time.Clock()
font = pygame.font.SysFont("Arial", 18)
class surface_water_particle():
def __init__(self, x,y):
self.x_pos = x
self.y_pos = y
self.target_y = y
self.velocity = 0
self.k = 0.02
self.d = 0.02
self.time = 1
def update(self):
x = -(self.target_y - self.y_pos)
a = -(self.k * x - self.d * self.velocity)
self.y_pos += self.velocity
self.velocity += a
class water():
def __init__(self, x_start, x_end, y_start, y_end, segment_length):
self.springs = []
self.x_start = x_start
self.y_start = y_start
self.x_end = x_end
self.y_end = y_end - 10
for i in range(abs(x_end - x_start) // segment_length):
self.springs.append(surface_water_particle(i * segment_length + x_start, y_end))
def update(self, spread):
for i in range(len(self.springs)):
self.springs[i].update()
leftDeltas = [0] * len(self.springs)
rightDeltas = [0] * len(self.springs)
for i in range(0, len(self.springs) ):
if i > 0:
leftDeltas[i] = spread * (self.springs[i].y_pos - self.springs[i - 1].y_pos)
self.springs[i - 1].velocity += leftDeltas[i]
if i < len(self.springs) - 1:
rightDeltas[i] = spread * (self.springs[i].y_pos - self.springs[i + 1].y_pos)
self.springs[i + 1].velocity += rightDeltas[i]
for i in range(0, len(self.springs) ):
if i > 0:
self.springs[i - 1].velocity += leftDeltas[i]
if i < len(self.springs) - 1:
self.springs[i + 1].velocity += rightDeltas[i]
def splash(self, index, speed):
if index > 0 and index < len(self.springs) :
self.springs[index].velocity = speed
def draw(self):
water_surface = pygame.Surface((abs(self.x_start - self.x_end), abs(self.y_start - self.y_end))).convert_alpha()
water_surface.fill((0,0,0,0))
water_surface.set_colorkey((0,0,0,0))
polygon_points = []
polygon_points.append((self.x_start, self.y_start))
for spring in range(len(self.springs)):
polygon_points.append((water_test.springs[spring].x_pos, water_test.springs[spring].y_pos))
polygon_points.append((water_test.springs[len(self.springs) - 1].x_pos, self.y_start))
#pygame.draw.polygon(water_surface, (0,0,255), polygon_points)
for spring in range(0,len(self.springs) - 1):
pygame.draw.line(screen, (0,0,255), (water_test.springs[spring].x_pos, water_test.springs[spring].y_pos), (water_test.springs[spring + 1].x_pos, water_test.springs[spring + 1].y_pos), 2)
#water_surface.set_alpha(100)
return water_surface
def update_fps():
fps_text = font.render(str(int(clock.get_fps())), 1, pygame.Color("coral"))
screen.blit(fps_text, (0,0))
water_test = water(0,800,200,80, 20)
while True:
screen.fill((255,255,255))
water_test.update(0.5)
screen.blit(water_test.draw(), (0,0))
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
if event.type == MOUSEBUTTONDOWN:
water_test.splash(10,0.1)
pygame.display.update()
clock.tick(60)
我尝试寻找与我们的教程实施有关的任何问题,但我找不到任何看起来不合适的地方。然后我尝试添加一个阻力函数,它会在每次更新时划分水流速度。然而,这并没有解决问题。
有谁知道哪里做错了以及如何解决这个问题?
我修改了代码以完全匹配示例,至少它不再立即爆炸了。
主要问题似乎是增量循环。调整 spring 更新的代码并将其注释掉会导致一个很好的振荡,它没有停止,但也没有爆炸。
外部循环似乎很重要,可以让一切变得轻松一些。
我认为这是因为增加了高度,因为改变高度会改变 spring 的力。
import pygame, random
import math as m
from pygame import *
pygame.init()
WINDOW_SIZE = (854, 480)
screen = pygame.display.set_mode(WINDOW_SIZE,0,32) # initiate the window
clock = pygame.time.Clock()
font = pygame.font.SysFont("Arial", 18)
class surface_water_particle():
def __init__(self, x,y):
self.x_pos = x
self.y_pos = y
self.target_y = y
self.velocity = 0
self.k = 0.02
self.d = 0.02
self.time = 1
def update(self):
x = self.y_pos - self.target_y #this was overly complicated for some reason
a = -self.k * x - self.d * self.velocity #here was a wrong sign for the dampening
self.y_pos += self.velocity
self.velocity += a
class water():
def __init__(self, x_start, x_end, y_start, y_end, segment_length):
self.springs = []
self.x_start = x_start
self.y_start = y_start
self.x_end = x_end
self.y_end = y_end - 10
for i in range(abs(x_end - x_start) // segment_length):
self.springs.append(surface_water_particle(i * segment_length + x_start, y_end))
def update(self, spread):
for i in range(len(self.springs)):
self.springs[i].update()
leftDeltas = [0] * len(self.springs)
rightDeltas = [0] * len(self.springs)
# adding this outer loop gave the real success
for j in range(0,8):
for i in range(0, len(self.springs) ):
if i > 0:
leftDeltas[i] = spread * (self.springs[i].y_pos - self.springs[i - 1].y_pos)
self.springs[i - 1].velocity += leftDeltas[i]
if i < len(self.springs) - 1:
rightDeltas[i] = spread * (self.springs[i].y_pos - self.springs[i + 1].y_pos)
self.springs[i + 1].velocity += rightDeltas[i]
# here you used velocity instead of height before
for i in range(0, len(self.springs) ):
if i > 0:
self.springs[i - 1].y_pos += leftDeltas[i]
if i < len(self.springs) - 1:
self.springs[i + 1].y_pos += rightDeltas[i]
def splash(self, index, speed):
if index > 0 and index < len(self.springs) :
self.springs[index].velocity = speed
def draw(self):
water_surface = pygame.Surface((abs(self.x_start - self.x_end), abs(self.y_start - self.y_end))).convert_alpha()
water_surface.fill((0,0,0,0))
water_surface.set_colorkey((0,0,0,0))
polygon_points = []
polygon_points.append((self.x_start, self.y_start))
for spring in range(len(self.springs)):
polygon_points.append((water_test.springs[spring].x_pos, water_test.springs[spring].y_pos))
polygon_points.append((water_test.springs[len(self.springs) - 1].x_pos, self.y_start))
#pygame.draw.polygon(water_surface, (0,0,255), polygon_points)
for spring in range(0,len(self.springs) - 1):
pygame.draw.line(screen, (0,0,255), (water_test.springs[spring].x_pos, water_test.springs[spring].y_pos), (water_test.springs[spring + 1].x_pos, water_test.springs[spring + 1].y_pos), 2)
#water_surface.set_alpha(100)
return water_surface
def update_fps():
fps_text = font.render(str(int(clock.get_fps())), 1, pygame.Color("coral"))
screen.blit(fps_text, (0,0))
water_test = water(0,800,200,80, 20)
while True:
screen.fill((255,255,255))
water_test.update(0.5)
screen.blit(water_test.draw(), (0,0))
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
if event.type == MOUSEBUTTONDOWN:
water_test.splash(10,0.1)
pygame.display.update()
clock.tick(60)
一般情况下,你想要的是加速度沿着高差的反方向(就是要恢复到原来的位置)。速度应该降低并最终回到 0。
使用您拥有的原始代码,即使没有执行传播的循环,速度也会增加而不是减少。
速度可能永远不会达到 0,因此您可能还应该添加一个阈值,将其设置为 0 以避免闪烁。
对于调试,请始终打印您的值并查看它们是否有意义。在这种情况下,您会注意到即使是单个 spring 更新步骤,速度也会上升。
编辑:
这个答案解决了这个问题,但没有调整参数以使其看起来不错。请参阅 答案以选择合适的参数。
许多与另一个相同的观察结果 post。我评论了几件事,稍微改变了你的速度声明。在“传播”中我添加了通行证。
修改了一些数字,得到了一些非常漂亮的飞溅效果。 (比公认的解决方案更好!:( 哈哈。
如果您需要进一步解决这个问题,我建议您注释掉扩张件(或给它一个零值),您可以自己检查水的弹性。这使得 T/S.
更容易
import pygame, random
import math as m
from pygame import *
pygame.init()
WINDOW_SIZE = (854, 480)
screen = pygame.display.set_mode(WINDOW_SIZE,0,32) # initiate the window
clock = pygame.time.Clock()
font = pygame.font.SysFont("Arial", 18)
class surface_water_particle():
k = 0.04 # spring constant
d = 0.08 # damping constant
def __init__(self, x,y):
self.x_pos = x
self.y_pos = y
self.target_y = y
self.velocity = 0
def update(self):
x = self.y_pos - self.target_y # displacement of "spring"
a = -self.k * x - self.d * self.velocity # unit of acceleration
self.y_pos += self.velocity
self.velocity += a
class water():
def __init__(self, x_start, x_end, y_start, y_end, segment_length):
self.springs = []
self.x_start = x_start
self.y_start = y_start
self.x_end = x_end
self.y_end = y_end - 10
for i in range(abs(x_end - x_start) // segment_length):
self.springs.append(surface_water_particle(i * segment_length + x_start, y_end))
def update(self, spread):
passes = 4 # more passes = more splash spreading
for i in range(len(self.springs)):
self.springs[i].update()
leftDeltas = [0] * len(self.springs)
rightDeltas = [0] * len(self.springs)
for p in range(passes):
for i in range(0, len(self.springs) -1 ):
if i > 0:
leftDeltas[i] = spread * (self.springs[i].y_pos - self.springs[i - 1].y_pos)
self.springs[i - 1].velocity += leftDeltas[i]
if i < len(self.springs) - 1:
rightDeltas[i] = spread * (self.springs[i].y_pos - self.springs[i + 1].y_pos)
self.springs[i + 1].velocity += rightDeltas[i]
for i in range(0, len(self.springs) -1):
if i > 0:
self.springs[i - 1].y_pos += leftDeltas[i] # you were updating velocity here!
if i < len(self.springs) - 1:
self.springs[i + 1].y_pos += rightDeltas[i]
def splash(self, index, speed):
if index > 0 and index < len(self.springs) :
self.springs[index].velocity = speed
def draw(self):
water_surface = pygame.Surface((abs(self.x_start - self.x_end), abs(self.y_start - self.y_end))).convert_alpha()
water_surface.fill((0,0,0,0))
water_surface.set_colorkey((0,0,0,0))
polygon_points = []
polygon_points.append((self.x_start, self.y_start))
for spring in range(len(self.springs)):
polygon_points.append((water_test.springs[spring].x_pos, water_test.springs[spring].y_pos))
polygon_points.append((water_test.springs[len(self.springs) - 1].x_pos, self.y_start))
#pygame.draw.polygon(water_surface, (0,0,255), polygon_points)
for spring in range(0,len(self.springs) - 1):
pygame.draw.line(screen, (0,0,255), (water_test.springs[spring].x_pos, water_test.springs[spring].y_pos), (water_test.springs[spring + 1].x_pos, water_test.springs[spring + 1].y_pos), 2)
#water_surface.set_alpha(100)
return water_surface
def update_fps():
fps_text = font.render(str(int(clock.get_fps())), 1, pygame.Color("coral"))
screen.blit(fps_text, (0,0))
water_test = water(0,800,200,200, 3)
while True:
screen.fill((255,255,255))
water_test.update(0.025)
screen.blit(water_test.draw(), (0,0))
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
if event.type == MOUSEBUTTONDOWN:
water_test.splash(50,100)
pygame.display.update()
clock.tick(60)
所以,我一直在尝试根据本教程pygame实现水物理
问题是,当我在 pygame 中实现此代码时,水决定它不会产生很好的涟漪效应,而是会疯狂地在屏幕上晃动,直到最终崩溃。
我在一些不和谐的服务器上四处寻找,发现其他人已经尝试实现同样的事情,但遇到了同样的问题。他们的代码基本上和我的一样,只是组织得更整齐,所以我会 post 它而不是我的。
import pygame, random
import math as m
from pygame import *
pygame.init()
WINDOW_SIZE = (854, 480)
screen = pygame.display.set_mode(WINDOW_SIZE,0,32) # initiate the window
clock = pygame.time.Clock()
font = pygame.font.SysFont("Arial", 18)
class surface_water_particle():
def __init__(self, x,y):
self.x_pos = x
self.y_pos = y
self.target_y = y
self.velocity = 0
self.k = 0.02
self.d = 0.02
self.time = 1
def update(self):
x = -(self.target_y - self.y_pos)
a = -(self.k * x - self.d * self.velocity)
self.y_pos += self.velocity
self.velocity += a
class water():
def __init__(self, x_start, x_end, y_start, y_end, segment_length):
self.springs = []
self.x_start = x_start
self.y_start = y_start
self.x_end = x_end
self.y_end = y_end - 10
for i in range(abs(x_end - x_start) // segment_length):
self.springs.append(surface_water_particle(i * segment_length + x_start, y_end))
def update(self, spread):
for i in range(len(self.springs)):
self.springs[i].update()
leftDeltas = [0] * len(self.springs)
rightDeltas = [0] * len(self.springs)
for i in range(0, len(self.springs) ):
if i > 0:
leftDeltas[i] = spread * (self.springs[i].y_pos - self.springs[i - 1].y_pos)
self.springs[i - 1].velocity += leftDeltas[i]
if i < len(self.springs) - 1:
rightDeltas[i] = spread * (self.springs[i].y_pos - self.springs[i + 1].y_pos)
self.springs[i + 1].velocity += rightDeltas[i]
for i in range(0, len(self.springs) ):
if i > 0:
self.springs[i - 1].velocity += leftDeltas[i]
if i < len(self.springs) - 1:
self.springs[i + 1].velocity += rightDeltas[i]
def splash(self, index, speed):
if index > 0 and index < len(self.springs) :
self.springs[index].velocity = speed
def draw(self):
water_surface = pygame.Surface((abs(self.x_start - self.x_end), abs(self.y_start - self.y_end))).convert_alpha()
water_surface.fill((0,0,0,0))
water_surface.set_colorkey((0,0,0,0))
polygon_points = []
polygon_points.append((self.x_start, self.y_start))
for spring in range(len(self.springs)):
polygon_points.append((water_test.springs[spring].x_pos, water_test.springs[spring].y_pos))
polygon_points.append((water_test.springs[len(self.springs) - 1].x_pos, self.y_start))
#pygame.draw.polygon(water_surface, (0,0,255), polygon_points)
for spring in range(0,len(self.springs) - 1):
pygame.draw.line(screen, (0,0,255), (water_test.springs[spring].x_pos, water_test.springs[spring].y_pos), (water_test.springs[spring + 1].x_pos, water_test.springs[spring + 1].y_pos), 2)
#water_surface.set_alpha(100)
return water_surface
def update_fps():
fps_text = font.render(str(int(clock.get_fps())), 1, pygame.Color("coral"))
screen.blit(fps_text, (0,0))
water_test = water(0,800,200,80, 20)
while True:
screen.fill((255,255,255))
water_test.update(0.5)
screen.blit(water_test.draw(), (0,0))
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
if event.type == MOUSEBUTTONDOWN:
water_test.splash(10,0.1)
pygame.display.update()
clock.tick(60)
我尝试寻找与我们的教程实施有关的任何问题,但我找不到任何看起来不合适的地方。然后我尝试添加一个阻力函数,它会在每次更新时划分水流速度。然而,这并没有解决问题。
有谁知道哪里做错了以及如何解决这个问题?
我修改了代码以完全匹配示例,至少它不再立即爆炸了。
主要问题似乎是增量循环。调整 spring 更新的代码并将其注释掉会导致一个很好的振荡,它没有停止,但也没有爆炸。
外部循环似乎很重要,可以让一切变得轻松一些。
我认为这是因为增加了高度,因为改变高度会改变 spring 的力。
import pygame, random
import math as m
from pygame import *
pygame.init()
WINDOW_SIZE = (854, 480)
screen = pygame.display.set_mode(WINDOW_SIZE,0,32) # initiate the window
clock = pygame.time.Clock()
font = pygame.font.SysFont("Arial", 18)
class surface_water_particle():
def __init__(self, x,y):
self.x_pos = x
self.y_pos = y
self.target_y = y
self.velocity = 0
self.k = 0.02
self.d = 0.02
self.time = 1
def update(self):
x = self.y_pos - self.target_y #this was overly complicated for some reason
a = -self.k * x - self.d * self.velocity #here was a wrong sign for the dampening
self.y_pos += self.velocity
self.velocity += a
class water():
def __init__(self, x_start, x_end, y_start, y_end, segment_length):
self.springs = []
self.x_start = x_start
self.y_start = y_start
self.x_end = x_end
self.y_end = y_end - 10
for i in range(abs(x_end - x_start) // segment_length):
self.springs.append(surface_water_particle(i * segment_length + x_start, y_end))
def update(self, spread):
for i in range(len(self.springs)):
self.springs[i].update()
leftDeltas = [0] * len(self.springs)
rightDeltas = [0] * len(self.springs)
# adding this outer loop gave the real success
for j in range(0,8):
for i in range(0, len(self.springs) ):
if i > 0:
leftDeltas[i] = spread * (self.springs[i].y_pos - self.springs[i - 1].y_pos)
self.springs[i - 1].velocity += leftDeltas[i]
if i < len(self.springs) - 1:
rightDeltas[i] = spread * (self.springs[i].y_pos - self.springs[i + 1].y_pos)
self.springs[i + 1].velocity += rightDeltas[i]
# here you used velocity instead of height before
for i in range(0, len(self.springs) ):
if i > 0:
self.springs[i - 1].y_pos += leftDeltas[i]
if i < len(self.springs) - 1:
self.springs[i + 1].y_pos += rightDeltas[i]
def splash(self, index, speed):
if index > 0 and index < len(self.springs) :
self.springs[index].velocity = speed
def draw(self):
water_surface = pygame.Surface((abs(self.x_start - self.x_end), abs(self.y_start - self.y_end))).convert_alpha()
water_surface.fill((0,0,0,0))
water_surface.set_colorkey((0,0,0,0))
polygon_points = []
polygon_points.append((self.x_start, self.y_start))
for spring in range(len(self.springs)):
polygon_points.append((water_test.springs[spring].x_pos, water_test.springs[spring].y_pos))
polygon_points.append((water_test.springs[len(self.springs) - 1].x_pos, self.y_start))
#pygame.draw.polygon(water_surface, (0,0,255), polygon_points)
for spring in range(0,len(self.springs) - 1):
pygame.draw.line(screen, (0,0,255), (water_test.springs[spring].x_pos, water_test.springs[spring].y_pos), (water_test.springs[spring + 1].x_pos, water_test.springs[spring + 1].y_pos), 2)
#water_surface.set_alpha(100)
return water_surface
def update_fps():
fps_text = font.render(str(int(clock.get_fps())), 1, pygame.Color("coral"))
screen.blit(fps_text, (0,0))
water_test = water(0,800,200,80, 20)
while True:
screen.fill((255,255,255))
water_test.update(0.5)
screen.blit(water_test.draw(), (0,0))
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
if event.type == MOUSEBUTTONDOWN:
water_test.splash(10,0.1)
pygame.display.update()
clock.tick(60)
一般情况下,你想要的是加速度沿着高差的反方向(就是要恢复到原来的位置)。速度应该降低并最终回到 0。
使用您拥有的原始代码,即使没有执行传播的循环,速度也会增加而不是减少。
速度可能永远不会达到 0,因此您可能还应该添加一个阈值,将其设置为 0 以避免闪烁。
对于调试,请始终打印您的值并查看它们是否有意义。在这种情况下,您会注意到即使是单个 spring 更新步骤,速度也会上升。
编辑:
这个答案解决了这个问题,但没有调整参数以使其看起来不错。请参阅
许多与另一个相同的观察结果 post。我评论了几件事,稍微改变了你的速度声明。在“传播”中我添加了通行证。
修改了一些数字,得到了一些非常漂亮的飞溅效果。 (比公认的解决方案更好!:( 哈哈。
如果您需要进一步解决这个问题,我建议您注释掉扩张件(或给它一个零值),您可以自己检查水的弹性。这使得 T/S.
更容易import pygame, random
import math as m
from pygame import *
pygame.init()
WINDOW_SIZE = (854, 480)
screen = pygame.display.set_mode(WINDOW_SIZE,0,32) # initiate the window
clock = pygame.time.Clock()
font = pygame.font.SysFont("Arial", 18)
class surface_water_particle():
k = 0.04 # spring constant
d = 0.08 # damping constant
def __init__(self, x,y):
self.x_pos = x
self.y_pos = y
self.target_y = y
self.velocity = 0
def update(self):
x = self.y_pos - self.target_y # displacement of "spring"
a = -self.k * x - self.d * self.velocity # unit of acceleration
self.y_pos += self.velocity
self.velocity += a
class water():
def __init__(self, x_start, x_end, y_start, y_end, segment_length):
self.springs = []
self.x_start = x_start
self.y_start = y_start
self.x_end = x_end
self.y_end = y_end - 10
for i in range(abs(x_end - x_start) // segment_length):
self.springs.append(surface_water_particle(i * segment_length + x_start, y_end))
def update(self, spread):
passes = 4 # more passes = more splash spreading
for i in range(len(self.springs)):
self.springs[i].update()
leftDeltas = [0] * len(self.springs)
rightDeltas = [0] * len(self.springs)
for p in range(passes):
for i in range(0, len(self.springs) -1 ):
if i > 0:
leftDeltas[i] = spread * (self.springs[i].y_pos - self.springs[i - 1].y_pos)
self.springs[i - 1].velocity += leftDeltas[i]
if i < len(self.springs) - 1:
rightDeltas[i] = spread * (self.springs[i].y_pos - self.springs[i + 1].y_pos)
self.springs[i + 1].velocity += rightDeltas[i]
for i in range(0, len(self.springs) -1):
if i > 0:
self.springs[i - 1].y_pos += leftDeltas[i] # you were updating velocity here!
if i < len(self.springs) - 1:
self.springs[i + 1].y_pos += rightDeltas[i]
def splash(self, index, speed):
if index > 0 and index < len(self.springs) :
self.springs[index].velocity = speed
def draw(self):
water_surface = pygame.Surface((abs(self.x_start - self.x_end), abs(self.y_start - self.y_end))).convert_alpha()
water_surface.fill((0,0,0,0))
water_surface.set_colorkey((0,0,0,0))
polygon_points = []
polygon_points.append((self.x_start, self.y_start))
for spring in range(len(self.springs)):
polygon_points.append((water_test.springs[spring].x_pos, water_test.springs[spring].y_pos))
polygon_points.append((water_test.springs[len(self.springs) - 1].x_pos, self.y_start))
#pygame.draw.polygon(water_surface, (0,0,255), polygon_points)
for spring in range(0,len(self.springs) - 1):
pygame.draw.line(screen, (0,0,255), (water_test.springs[spring].x_pos, water_test.springs[spring].y_pos), (water_test.springs[spring + 1].x_pos, water_test.springs[spring + 1].y_pos), 2)
#water_surface.set_alpha(100)
return water_surface
def update_fps():
fps_text = font.render(str(int(clock.get_fps())), 1, pygame.Color("coral"))
screen.blit(fps_text, (0,0))
water_test = water(0,800,200,200, 3)
while True:
screen.fill((255,255,255))
water_test.update(0.025)
screen.blit(water_test.draw(), (0,0))
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
if event.type == MOUSEBUTTONDOWN:
water_test.splash(50,100)
pygame.display.update()
clock.tick(60)