Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pygame water physics not working as intended

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?

like image 521
Evelyn Avatar asked Jun 21 '20 14:06

Evelyn


1 Answers

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)
like image 134
AirSquid Avatar answered Oct 13 '22 18:10

AirSquid