Физика воды Pygame не работает должным образом - PullRequest
5 голосов
/ 21 июня 2020

Итак, я пытался реализовать физику воды в pygame на основе этого руководства

https://gamedevelopment.tutsplus.com/tutorials/make-a-splash-with-dynamic-2d-water-effects--gamedev-236

Проблема в том, что когда я реализовал этот код в pygame, вода решает, что вместо того, чтобы иметь приятный эффект пульсации, он будет go сумасшедшим и будет покачиваться по всему экрану, пока в конечном итоге не выйдет из строя. пытался реализовать то же самое, и возникла та же проблема. Их код в основном такой же, как мой, только более аккуратно организован, поэтому я опубликую его вместо своего.

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)

Я пытался найти какие-либо проблемы с нашими реализациями учебника, но не смог найти ничего, что казалось не к месту. Затем я попытался добавить функцию сопротивления, которая делит скорость воды при каждом обновлении. Однако это не устранило проблему.

Кто-нибудь знает, что было сделано неправильно и как это исправить?

Ответы [ 2 ]

3 голосов
/ 21 июня 2020

Многие из тех же наблюдений, что и в другом посте. Я прокомментировал несколько вещей, немного изменил ваше заявление о скорости. И в "распространении" я добавил проходы.

Немного повозился с числами и получил несколько действительно хороших брызг. (лучше, чем принятое решение! :( lol.

Если вам нужно еще немного устранить неполадки, я бы посоветовал вам закомментировать распространяющуюся деталь (или присвоить ей нулевое значение), и вы можете проверить упругость воды сама по себе. Это значительно упростило процедуру 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)
2 голосов
/ 21 июня 2020

Я изменил код, чтобы он точно соответствовал примеру, и, по крайней мере, он больше не взрывается мгновенно.

Основная проблема, казалось, заключалась в циклах с дельтами. Корректировка кода для весеннего обновления и их комментарии приводят к приятным колебаниям, которые не прекращались, но и не взрывались.

Внешний l oop кажется важным, чтобы немного облегчить все .

Я бы подумал, что это из-за добавленной высоты, поскольку изменение высоты изменяет силу пружины.

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, чтобы избежать мерцания.

Для отладки всегда печатайте свои значения и посмотрите, имеют ли они смысл. В этом случае вы могли бы заметить, что скорость увеличивается даже для одного шага обновления пружины.

Изменить: этот ответ устраняет проблему, но не настраивает параметры, чтобы она выглядела хорошо. См. Ответ Джеффа Х. , чтобы узнать о хорошем выборе параметров.

...