I have a working code for buoyancy simulation but it's not behaving properly. I have 3 scenarios and the expected behavior as follows:
- Object initial position is already submerged : (a) expected to move up; (b) then down but not beyond its previous submersion depth; (c) repeat a-b until object stops or floats at surface level;
- Object initial position is at surface level : (a) remains at that surface level or have a floating effect;
- Object initial position above the surface level : (a) object falls down to the water, until it reaches a certain depth; (b) expected to move up; (c) then down but not beyond its previous submersion depth; (d) repeat b-c until object stops or floats at surface level;
To visualize the above expectations (particularly scenario 3), you may refer to this video (https://www.youtube.com/watch?v=Z_vfP_S5wis) and skip to 00:06:00 time frame.
I also have the code as follows:
import pygame
import sys
def get_overlapping_area(rect1, rect2):
overlap_width = min(rect1.right, rect2.right) - max(rect1.left, rect2.left)
overlap_height = min(rect1.bottom, rect2.bottom) - max(rect1.top, rect2.top)
return overlap_width * overlap_height
class Player(pygame.sprite.Sprite):
def __init__(self, pos):
super().__init__()
self.image = pygame.Surface((16, 32))
self.image.fill((0, 0, 0))
self.rect = self.image.get_rect(center=pos)
self.y_vel = 0
def apply_gravity(self):
self.y_vel += GRAVITY
def check_buoyancy_collisions(self):
buoyant_force = 0
for sprite in buoyant_group:
if sprite.rect.colliderect(self.rect):
submerged_area = get_overlapping_area(sprite.rect, self.rect)
buoyant_force -= sprite.buoyancy * GRAVITY * submerged_area
self.y_vel += buoyant_force
def update(self):
self.apply_gravity()
self.check_buoyancy_collisions()
self.rect.top += self.y_vel
def draw(self, screen: pygame.display):
screen.blit(self.image, self.rect)
class Fluid(pygame.sprite.Sprite):
def __init__(self, pos, tile_size):
super().__init__()
self.image = pygame.Surface((tile_size, tile_size))
self.image.fill((0, 255, 255))
self.rect = self.image.get_rect(topleft=pos)
self.buoyancy = 0.00225
pygame.init()
WIDTH, HEIGHT = 500, 700
screen = pygame.display.set_mode((WIDTH, HEIGHT))
TILE_SIZE = 32
GRAVITY = 0.05
buoyant_group = pygame.sprite.Group()
player_y = HEIGHT * 3 // 4 # player drop point few pixels below fluid surface
# player_y = HEIGHT // 4 # player drop point at fluid surface
# player_y = HEIGHT // 8 # player drop point few pixels above fluid surface
# Instantiate Player and Fluid objects
player = Player((WIDTH // 2, player_y))
for r in range(HEIGHT // 4, HEIGHT * 2, TILE_SIZE):
for c in range(0, WIDTH, TILE_SIZE):
buoyant_group.add(Fluid(pos=(c, r), tile_size=TILE_SIZE))
# Game Loop
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
player.update()
screen.fill((100, 100, 100))
buoyant_group.draw(surface=screen)
player.draw(screen=screen)
pygame.display.update()
Few explanations for the code:
get_overlapping_area function : The buoyant force is computed as fluid weight density x displaced fluid volume, and since I'm dealing with 2D the overlapping area between the object and the fluid is computed rather than the displaced fluid volume.
Player class : A 16x32 sprite affected by both gravity and buoyant force. It has a check_buoyancy_collisions function responsible for the computation of the upward force on the object.
Fluid class : A 32x32 sprite responsible for the buoyancy. It has the self.buoyancy attribute calculated enough to overcome the Player object's weight due to gravity, thus an upward resultant force acting on the Player object.
player_y varaible : I have provided 3 set-ups to simulate the 3 scenarios mentioned above. You may comment/uncomment them accordingly.
PS: I have been on this problem for a week now. Most resources I found talks about the theory. The remaining few resources uses game engines that does the physics for them.