How to draw bubbles and turn them animated into circles
Asked Answered
H

1

6

I am trying to make a python program to draw a line and turn it into a circle with an animation using pygame, yet I haven't even gotten through the drawing-the-line code. I have noticed that python is changing the wrong or both items in a list that contains the starting point when the user presses down the left click, stored as the first item, and the current point of the user's mouse as the second.

This is generally what I want it to do: https://youtu.be/vlqZ0LubXCA

Here are the outcomes with and without the lines that update the 2nd item:

with:

The code will draw a single pixel at the mouse's position every frame instead of a line from the starting position to the mouse's position

without:

Without the lines, the previous frame isn't covered and continuously draws new lines over top every frame from the starting position to the mouse

As you can see, or read in the descriptions, the line is necessary to cover the previous frame.

I have marked the lines that change the outcome with arrows:

import pygame, PIL, random
print('\n')

#data
bubbles = []
color_options = [[87, 184, 222]]

pressed = False
released = False
bubline_start = []

background = [50, 25, 25]
size = [500, 500]

#pygame
display = pygame.display.set_mode(size)
pygame.init()

#functions
def new_bub_color():
    color_index = random.randint(0, len(color_options)-1)
    lvl = random.randrange(85, 115)

    bub_color = []
    for val in color_options[color_index]:
        bub_color.append(val*(lvl/100))
    return bub_color


def bubble_line():
    global display, pressed, bubline_start, released, bubbles, color_options
    
    if len(bubbles) > 0:
        if not bubbles[-1][0] == 0:
            #first frame of click
            bub_color = new_bub_color()

            bubbles.append([0, bub_color, [bubline_start, list(pygame.mouse.get_pos())]])
            pygame.draw.line(display, bub_color, bubline_start, pygame.mouse.get_pos())
        else:
            #draw after drags
            pygame.draw.line(display, bubbles[-1][1], bubbles[-1][2][0], list(pygame.mouse.get_pos()))            
            bubbles[-1][2][1] = list(pygame.mouse.get_pos())# <-- HERE
    else:
        #first bubble
        bub_color = new_bub_color()
        
        bubbles.append([0, bub_color, [bubline_start, list(pygame.mouse.get_pos())]])
        pygame.draw.line(display, bub_color, bubline_start, pygame.mouse.get_pos())

    if released:
        bubbles[-1][0] = 1
        bubbles[-1][2][1] = list(pygame.mouse.get_pos())# <-- HERE
        released = False


def cover_prev_frame():
    global bubbles, background, size
    min_pos = []
    max_pos = []

    for bubble in bubbles:
        min_pos = bubble[2][0]
        max_pos = bubble[2][0]

        for point in bubble[2]:
            #x min and max
            if point[0] < min_pos[0]:
                min_pos[0] = point[0]
            elif point[0] > max_pos[0]:
                max_pos[0] = point[0]

            #y min and max
            if point[1] < min_pos[1]:
                min_pos[1] = point[1]
            elif point[1] > max_pos[1]:
                max_pos[1] = point[1]
        max_pos = [max_pos[0]-min_pos[0]+1, max_pos[1]-min_pos[1]+1]

        if type(background) == str:
            #image background
            later = True

        elif type(background) == list:
            #solid color background
            pygame.draw.rect(display, background, pygame.Rect(min_pos, max_pos))


while True:
    pygame.event.pump()
    events = pygame.event.get()

    for event in events:
        if event.type == pygame.QUIT:
            pygame.quit()

        elif event.type == pygame.MOUSEBUTTONDOWN and not pressed:
            bubline_start = list(pygame.mouse.get_pos())
            pressed = True
            
        elif event.type == pygame.MOUSEBUTTONUP and pressed:
            pressed = False
            released = True

    cover_prev_frame()
    if pressed or released:
        bubble_line()

    try:
        pygame.display.update()
    except:
        break

Hutson answered 20/7, 2022 at 17:16 Comment(4)
You only have one bubline_start list in your code. EVERY ENTRY in the bubbles array has a reference to that one list. If you change bubline_start, that changes every entry in the list. I suspect you want bubline_start[:] to make a new copy.Sivia
I didn't know that python did that, I'm assuming a part of 3.x. But the issue is still there, the lines that I marked are still changing the value of the first item in the list, when I wrote it to change the 2nd, and only the 2nd.Hutson
FWIW, that's how Python has worked since the very beginning. It is one of the most common Python programmer errors. You might consider creating a class Bubble to hold all of the bubble state information so you don't have to do [-1][2][1].Sivia
What is it that you want this to draw?Sivia
A
18

if not bubbles[-1][0] == 0: is False as long as the mouse is not released. Therefore add many line segments, each starting at bubline_start and ending at the current mouse position.
You must redraw the scene in each frame. bubbles is a list of bubbles and each bubble has a list of points. Add a new point to the last bubble in the list while the mouse is held down. Start a new bubble when the mouse is pressed and end a bubble when it is released. This greatly simplifies your code.

Minimal example

import pygame, random

size = [500, 500]
pygame.init()
display = pygame.display.set_mode(size)
clock = pygame.time.Clock()

pressed = False
bubbles = []
background = [50, 25, 25]

run = True
while run:
    clock.tick(100)
    events = pygame.event.get()
    for event in events:
        if event.type == pygame.QUIT:
            run = False

        elif event.type == pygame.MOUSEBUTTONDOWN:
            start_pos = list(event.pos)
            bubble_color = pygame.Color(0)
            bubble_color.hsla = (random.randrange(0, 360), 100, 50, 100)
            bubbles.append((bubble_color, [start_pos]))
            pressed = True

        elif event.type == pygame.MOUSEMOTION and pressed:
            new_pos = list(event.pos)
            if len(bubbles[-1][1]) > 0 and bubbles[-1][1] != new_pos:  
                bubbles[-1][1].append(new_pos)
            
        elif event.type == pygame.MOUSEBUTTONUP:
            pressed = False
            end_pos = list(event.pos)
            if len(bubbles[-1][1]) > 0 and bubbles[-1][1] != end_pos:  
                bubbles[-1][1].append(list(event.pos))

    display.fill(background)
    for i, bubble in enumerate(bubbles):
        if len(bubble[1]) > 1:
            closed = not pressed or i < len(bubbles) - 1
            pygame.draw.lines(display, bubble[0], closed, bubble[1], 3)
    pygame.display.update()

pygame.quit()

For the animation I propose to create a class that represents a bubble and a method animate that slowly turns the polygon into a circle.

Minimal example

import pygame, random

size = [500, 500]
pygame.init()
display = pygame.display.set_mode(size)
clock = pygame.time.Clock()

class Bubble:
    def __init__(self, start):
        self.color = pygame.Color(0)
        self.color.hsla = (random.randrange(0, 360), 100, 50, 100)
        self.points = [list(start)]
        self.closed = False
        self.finished = False
    def add_point(self, point, close):
        self.points.append(list(point))
        self.closed = close
        if self.closed:
            x_, y_ = list(zip(*self.points))
            x0, y0, x1, y1 = min(x_), min(y_), max(x_), max(y_)
            rect = pygame.Rect(x0, y0, x1-x0, y1-y0)
            self.center = rect.center
            self.radius = max(*rect.size) // 2
    def animate(self):
        if self.closed and not self.finished:
            cpt = pygame.math.Vector2(self.center) + (0.5, 0.5)
            self.finished = True
            for i, p in enumerate(self.points):
                pt = pygame.math.Vector2(p)
                v = pt - cpt
                l = v.magnitude()
                if l + 0.5 < self.radius:
                    self.finished = False
                v.scale_to_length(min(self.radius, l+0.5))
                pt = cpt + v
                self.points[i] = [pt.x, pt.y]
                
    def draw(self, surf):
        if self.finished:
            pygame.draw.circle(surf, self.color, self.center, self.radius, 3)
        elif len(self.points) > 1:
            pygame.draw.lines(surf, self.color, self.closed, self.points, 3)

bubbles = []
pressed = False
background = [50, 25, 25]

run = True
while run:
    clock.tick(100)
    events = pygame.event.get()
    for event in events:
        if event.type == pygame.QUIT:
            run = False

        elif event.type == pygame.MOUSEBUTTONDOWN:
            bubbles.append(Bubble(event.pos))
            pressed = True

        elif event.type == pygame.MOUSEMOTION and pressed:
            bubbles[-1].add_point(event.pos, False)
            
        elif event.type == pygame.MOUSEBUTTONUP:
            bubbles[-1].add_point(event.pos, True)
            pressed = False

    for bubble in bubbles:
        bubble.animate()

    display.fill(background)
    for bubble in bubbles:
        bubble.draw(display)
    pygame.display.update()

pygame.quit()
Anse answered 26/7, 2022 at 18:40 Comment(3)
I have updated the post to include a general idea pf what I am trying to do. Although, great job with the speed of the answer and code, but that isn't what I was going for.Hutson
Are you looking for something like that Looping mousebutton down to draw linesAnse
. . Wow. Your edit improved and finished my entire project.Hutson

© 2022 - 2024 — McMap. All rights reserved.