Python and Turtle animation,colorsys,Difficulty Level 6,events,python,timer events,Tutorial Tutorial: Gradually Lighting Up Animation with Python Turtle

Tutorial: Gradually Lighting Up Animation with Python Turtle

In this tutorial we are going present how to develop gradually lighting up animation. This is a series of tutorials leading to the development of synchronized fireflies.

When animating, especially if you want control the speed of animation, you need stick to the principle of model-view separation. This means you have one part of program that controls the state of objects (model), and the other part that draws the objects (view). State of objects may include position, heading, color, and other properties. You will know what it means better by doing more practice problems. The part of program that changes objects’ states is not concerned with drawing of the objects. The drawing is done by separate part of program that checks the states of the objects and render them on the screen. Let’s stop taking and get into the development of this animation.

Let’s develop the model part first. As you can observe the only thing that is changing is the color, more specifically the brightness of the circle. It is much easier to control the brightness of a color with HSV (Hue-Saturation-Value) color model, which can be converted to RGB color for Turtle easily with Python’s colorsys library. We will choose hue value of 0.22, which is greenish yellow color that is similar to the color of fireflies. Let’s suppose the firefly’s lighting up cycle is 5 seconds and it takes 1 second to gradually light up and dim back to the original state. So, between time 0 to 4, the brightness value stays at initial low value; then from time 4.0 to 4.5, the brightness gradually increases to maximum value, and from time 4.5 to 5, the brightness gradually goes down from the maximum brightness to initial dark state.

We will use Turtle’s timer event to update this brightness value. The question is: how frequently should we update this value? To see a smooth animation, we need to draw about 30 times per second: this number is called frames per second (fps). It is possible that your computer may not be able to draw 30 times per second especially when there are many complex objects. In this case, you will see stuttering animation. Coming back to deciding on how frequently we should update the values, since we will use 30 fps, we will update every 1/30 second.

The following is the code that uses timer event to set the brightness value. Most of the code is self-explanatory with the help of comments. There are two global variables: t and v. The variable t is current cycle time (0 – CYCLE) in seconds. Every time, the timer function is called, this value increments by the amount of timer value. Variable v represents the current brightness value, which can be computed easily knowing current time t. You can uncomment print(t,v) to see the pair of values at every update and check if they are correct.

import turtle

screen = turtle.Screen()
screen.setup(1000,1000)
screen.title("Gradually Lighting Up - PythonTurtle.Academy")
turtle.hideturtle()
turtle.speed(0)
turtle.tracer(0,0)

# Constants
H_YELLOWGREEN = 0.22 # constant: hue value of yellow green color.
V_DARK = 0.1 # constant: brightness value of initial dark state
V_BRIGHT = 1 # constant: brightness value of the brightest state
FPS = 30 # constant: refresh about 30 times per second
TIMER_VALUE = 1000//FPS # the timer value in milliseconds for timer events
CYCLE = 5 # costant: 5 second cycle for firefly to light up
LIGHTUP_TIME = 1 # constant: 1 second light up and dim

# Variables
v = V_DARK # initial brightness state
t = 0 # current time

def update_brightness():
    global t,v
    t += TIMER_VALUE/1000 # every time this function is called, time increases by this value
    if t > CYCLE:
        t -= CYCLE # make sure time stays within CYCLE
    if t < CYCLE-LIGHTUP_TIME:
        v = V_DARK # dormant period
    elif t < CYCLE-LIGHTUP_TIME/2: # gradually (linearly) lighting up period
        v = V_DARK + (V_BRIGHT-V_DARK)*(t-(CYCLE-LIGHTUP_TIME))/(LIGHTUP_TIME/2)
    else: # gradually (linearly) dimming period
        v = V_BRIGHT - (V_BRIGHT-V_DARK)*(t-(CYCLE-LIGHTUP_TIME/2))/(LIGHTUP_TIME/2)

    #print(t,v)
    screen.ontimer(update_brightness,TIMER_VALUE)
                
screen.ontimer(update_brightness,TIMER_VALUE)

With model part working correctly, it’s time to add the drawing part. We will make a function that draw a dot at the center of the screen. The question is: when should this function be called? We can make an infinite loop, in which this function is called constantly. The problem is: it will waste a lot of CPU time redrawing the same thing unnecessarily. Only when the state of the object has changed this function will draw the dot. So the solution is to make global variable called should_draw. The update_brightness() function in code above is responsible for setting should_draw to True, when it finds the v has changed.

The following is the complete code for this project. Again, most of the code is self-explanatory with the help of comments. Run this program and see if it works correctly!

import turtle
import colorsys

screen = turtle.Screen()
screen.setup(1000,1000)
screen.title("Gradually Lighting Up - PythonTurtle.Academy")
turtle.hideturtle()
turtle.speed(0)
turtle.tracer(0,0)

screen.bgcolor('black')
firefly = turtle.Turtle() # turtle for drawing the lighting dot
firefly.hideturtle()
firefly.up()

# Constants
H_YELLOWGREEN = 0.22 # constant: hue value of yellow green color.
V_DARK = 0.1 # constant: brightness value of initial dark state
V_BRIGHT = 1 # constant: brightness value of the brightest state
FPS = 30 # constant: refresh about 30 times per second
TIMER_VALUE = 1000//FPS # the timer value in milliseconds for timer events
CYCLE = 5 # costant: 5 second cycle for firefly to light up
LIGHTUP_TIME = 1 # constant: 1 second light up and dim

# Variables
v = V_DARK # initial brightness state
t = 0 # current time
should_draw = True # this variable is used to decide if drawing is necessary to save CPU time

def update_brightness():
    global t,v,should_draw
    t += TIMER_VALUE/1000 # every time this function is called, time increases by this value
    lastv = v # lastv is v before potential change
    if t > CYCLE:
        t -= CYCLE # make sure time stays within CYCLE
    if t < CYCLE-LIGHTUP_TIME:
        v = V_DARK # dormant period
    elif t < CYCLE-LIGHTUP_TIME/2: # gradually (linearly) lighting up period
        v = V_DARK + (V_BRIGHT-V_DARK)*(t-(CYCLE-LIGHTUP_TIME))/(LIGHTUP_TIME/2)
    else: # gradually (linearly) dimming period
        v = V_BRIGHT - (V_BRIGHT-V_DARK)*(t-(CYCLE-LIGHTUP_TIME/2))/(LIGHTUP_TIME/2)

    if v != lastv:
        should_draw = True # v has changed, so set should_draw to True
#    print(t,v)
    screen.ontimer(update_brightness,TIMER_VALUE)

def draw():
    global v,firefly,should_draw
    if should_draw == False: # There is no change. Don't draw and return immediately
        return
    firefly.clear() # clear the current drawing
    color = colorsys.hsv_to_rgb(H_YELLOWGREEN,1,v) # use colorsys to convert HSV to RGB color
    firefly.color(color)
    firefly.dot(400)
    should_draw = False # just finished drawing, set should_draw to False
    
screen.ontimer(update_brightness,TIMER_VALUE)
while True:
    draw() # draw forever
    screen.update()

Related Tutorials:

Related Post