So, I have been trying to implement water physics in pygame based on this tutorial
https://gamedevelopment.tutsplus.com/tutorials/make-a-splash-with-dynamic-2d-water-effects--gamedev-236
The issue is, when I implemented this code in pygame, the water decides that instead of having a nice ripple effect, its going to go crazy and wiggle all over the screen until it eventually crashes.
I looked around on some discord server and found someone else had tried to implement the same thing, and had got the same problem. Their code is basically the same as mine just more neatly organized so I will post it instead of mine.
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)
I tried looking for any issues with our implementations of the tutorial but I could not find anything that seemed out of place. I then tried adding a resistance function that would divide the water velocity each time it updates. This however did not fix the issue.
Does anyone know what was done wrong and how to fix this?
Many of the same observations as the other post. I commented a few things up, changed your velocity statement a bit. And in the "spreading" I added the passes.
Tinkered with the numbers a bit, and got some real nice splashes. (better than the accepted solution! :( lol.
If you need to troubleshoot this some more, I would suggest you comment out the spreading piece (or give it a value of zero) and you can check the springiness of the water by itself. This made it much easier to 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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With