I want to slowly draw a line in python so the act of drawing is actually visible to the naked eye.
I tried making it by putting it in a loop and increasing the distance each time but i never had any success with it. The thing is nothing would appear for 3 sec and then the whole line would appear which is the opposite of what i want to accomplish. I didn't have success with a pygame.display.delay()
function either. The only thing that kind of worked is having clock.tick set to some abysmal values such as clock.tick(300000)
but this would just make the entire program really laggy.
def draw_red_line(i):
y = 0
while y < 300:
pygame.draw.line(screen, RED, (i*100+50, 0), (i*100+50, y))
y+=0.01
Using a sleep is not a good idea in this sort of situation, since it slows the whole thread (which is the entire program in a single-thread model).
It's better to keep some kind of state information about the line, and based on real-time timings (e.g.: elapsed milliseconds) progress the "growth" of the line, second by second.
This means the line needs to be broken into segments, and the smallest line segment is a single pixel. Using the Midpoint Line Algorithm, is an efficient way to determine all the pixels that lay on a line. Once all the "line parts" have been determined, it's possible to simply update the end-point of the line based on the elapsed time.
Here's some code I wrote earlier that, given a pair of points, returns a list of pixels.
midpoint.py
:
def __plotLineLow( x0,y0, x1,y1 ):
points = []
dx = x1 - x0
dy = y1 - y0
yi = 1
if dy < 0:
yi = -1
dy = -dy
D = 2*dy - dx
y = y0
for x in range( x0, x1 ):
points.append( (x,y) )
if D > 0:
y = y + yi
D = D - 2*dx
D = D + 2*dy
return points
def __plotLineHigh( x0,y0, x1,y1 ):
points = []
dx = x1 - x0
dy = y1 - y0
xi = 1
if dx < 0:
xi = -1
dx = -dx
D = 2*dx - dy
x = x0
for y in range( y0, y1 ):
points.append( (x,y) )
if D > 0:
x = x + xi
D = D - 2*dy
D = D + 2*dx
return points
def linePoints( pointA, pointB ):
""" Generate a list of integer points on the line pointA -> pointB """
x0, y0 = pointA
x1, y1 = pointB
points = []
if ( abs(y1 - y0) < abs(x1 - x0) ):
if ( x0 > x1 ):
points += __plotLineLow( x1, y1, x0, y0 )
else:
points += __plotLineLow( x0, y0, x1, y1 )
else:
if ( y0 > y1 ):
points += __plotLineHigh( x1, y1, x0, y0 )
else:
points += __plotLineHigh( x0, y0, x1, y1 )
return points
if __name__ == "__main__":
#midPoint( (597, 337), (553, 337) )
print( str( linePoints( (135, 295), (135, 304) ) ) )
And some demonstration code which implements a SlowLine
class.
import pygame
import random
import time
import sys
from midpoint import linePoints # Midpoint line algorithm
# Window size
WINDOW_WIDTH = 400
WINDOW_HEIGHT = 400
SKY_BLUE = ( 30, 30, 30)
SKY_RED = (200, 212, 14)
# Global millisecond count since start
NOW_MS = 0
class SlowLine():
def __init__( self, pixels_per_second, x0,y0, x1,y1, colour=SKY_RED ):
self.points = linePoints( ( x0, y0 ), ( x1, y1 ) )
self.pixel_count = len( self.points )
self.speed = pixels_per_second
self.start_point = self.points[0] # start with a single-pixel line
self.end_point = self.points[0]
self.pixel_cursor = 0 # The current end-pixel
self.last_update = 0 # Last time we updated
self.colour = colour
self.fully_drawn = False
def update(self):
global NOW_MS
if ( self.fully_drawn == True ):
# nothing to do
pass
else:
# How many milliseconds since the last update() call?
if ( self.last_update == 0 ):
self.last_update = NOW_MS
time_delta = 0
else:
time_delta = NOW_MS - self.last_update
self.last_udpate = NOW_MS
# New pixels to add => speed * time
new_pixel_count = time_delta * self.speed / 1000 # this may loose precision with very small speeds
if ( new_pixel_count + self.pixel_cursor > self.pixel_count ):
# We're out of pixels
self.end_point = self.points[-1]
self.full_drawn = True
else:
# Grow the line by <new_pixel_count> pixels
self.pixel_cursor += new_pixel_count
self.end_point = self.points[ int( self.pixel_cursor ) ]
def draw( self, screen ):
pygame.draw.line( screen, self.colour, self.start_point, self.end_point )
### MAIN
pygame.init()
SURFACE = pygame.HWSURFACE|pygame.DOUBLEBUF|pygame.RESIZABLE
WINDOW = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), SURFACE )
pygame.display.set_caption("Slow Line Movement")
# Create some random lines
lines = []
for i in range( 20 ):
rand_speed = random.randint( 1, 50 )
rand_x0 = random.randint( 0, WINDOW_WIDTH )
rand_y0 = random.randint( 0, WINDOW_HEIGHT )
rand_x1 = random.randint( 0, WINDOW_WIDTH )
rand_y1 = random.randint( 0, WINDOW_HEIGHT )
lines.append( SlowLine( rand_speed, rand_x0, rand_y0, rand_x1, rand_y1 ) )
# Main event loop
clock = pygame.time.Clock()
done = False
while not done:
NOW_MS = pygame.time.get_ticks()
# Update the line lengths
for l in lines:
l.update()
# Handle user-input
for event in pygame.event.get():
if ( event.type == pygame.QUIT ):
done = True
# Movement keys
keys = pygame.key.get_pressed()
if ( keys[pygame.K_UP] ):
print("up")
elif ( keys[pygame.K_DOWN] ):
print("down")
elif ( keys[pygame.K_LEFT] ):
print("left")
elif ( keys[pygame.K_RIGHT] ):
print("right")
elif ( keys[pygame.K_q] and ( keys[pygame.K_RCTRL] or keys[pygame.K_LCTRL] ) ):
print("^Q")
done = True
# Update the window, but not more than 60fps
WINDOW.fill( SKY_BLUE )
for l in lines:
l.draw( WINDOW )
pygame.display.flip()
# Clamp FPS
clock.tick_busy_loop(60)
pygame.quit()
In this animation the progress is a bit jerky, but that's the animation, not the demo.
You must display.flip()
to update the display and let the window events to be processed using event.get()
:
def draw_red_line(i):
y = 0
while y < 300:
pygame.draw.line(screen, RED, (i*100+50, 0), (i*100+50, y))
pygame.display.flip()
pygame.event.get()
y+=1
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