6 min read

(For more resources on Python, see here.)

Single image animation

Imagine that you are creating a cartoon movie where you want to animate the motion of an arrow or a bullet hitting a target. In such cases, typically it is just a single image. The desired animation effect is accomplished by performing appropriate translation or rotation of the image.

Time for action – bouncing ball animation

Lets create a simple animation of a ‘bouncing ball’. We will use a single image file, ball.png, which can be downloaded from the Packt website. The dimensions of this image in pixels are 200×200, created on a transparent background. The following screenshot shows this image opened in GIMP image editor. The three dots on the ball identify its side. We will see why this is needed. Imagine this as a ball used in a bowling game.

Python Multimedia: Animations Examples using Pyglet

The image of a ball opened in GIMP appears as shown in the preceding image. The ball size in pixels is 200×200.

  1. Download the files SingleImageAnimation.py and ball.png from the Packt website. Place the ball.png file in a sub-directory ‘images’ within the directory in which SingleImageAnimation.py is saved.
  2. The following code snippet shows the overall structure of the code.

    1 import pyglet
    2 import time
    3
    4 class SingleImageAnimation(pyglet.window.Window):
    5 def __init__(self, width=600, height=600):
    6 pass
    7 def createDrawableObjects(self):
    8 pass
    9 def adjustWindowSize(self):
    10 pass
    11 def moveObjects(self, t):
    12 pass
    13 def on_draw(self):
    14 pass
    15 win = SingleImageAnimation()
    16 # Set window background color to gray.
    17 pyglet.gl.glClearColor(0.5, 0.5, 0.5, 1)
    18
    19 pyglet.clock.schedule_interval(win.moveObjects, 1.0/20)
    20
    21 pyglet.app.run()

    Although it is not required, we will encapsulate event handling and other functionality within a class SingleImageAnimation. The program to be developed is short, but in general, it is a good coding practice. It will also be good for any future extension to the code. An instance of SingleImageAnimation is created on line 14. This class is inherited from pyglet.window.Window. It encapsulates the functionality we need here. The API method on_draw is overridden by the class. on_draw is called when the window needs to be redrawn. Note that we no longer need a decorator statement such as @win.event above the on_draw method because the window API method is simply overridden by this inherited class.

  3. The constructor of the class SingleImageAnimation is as follows:

    1 def __init__(self, width=None, height=None):
    2 pyglet.window.Window.__init__(self,
    3 width=width,
    4 height=height,
    5 resizable = True)
    6 self.drawableObjects = []
    7 self.rising = False
    8 self.ballSprite = None
    9 self.createDrawableObjects()
    10 self.adjustWindowSize()

    As mentioned earlier, the class SingleImageAnimation inherits pyglet.window.Window. However, its constructor doesn’t take all the arguments supported by its super class. This is because we don’t need to change most of the default argument values. If you want to extend this application further and need these arguments, you can do so by adding them as __init__ arguments. The constructor initializes some instance variables and then calls methods to create the animation sprite and resize the window respectively.

  4. The method createDrawableObjects creates a sprite instance using the ball.png image.

    1 def createDrawableObjects(self):
    2 """
    3 Create sprite objects that will be drawn within the
    4 window.
    5 """
    6 ball_img= pyglet.image.load('images/ball.png')
    7 ball_img.anchor_x = ball_img.width / 2
    8 ball_img.anchor_y = ball_img.height / 2
    9
    10 self.ballSprite = pyglet.sprite.Sprite(ball_img)
    11 self.ballSprite.position = (
    12 self.ballSprite.width + 100,
    13 self.ballSprite.height*2 - 50)
    14 self.drawableObjects.append(self.ballSprite)

    The anchor_x and anchor_y properties of the image instance are set such that the image has an anchor exactly at its center. This will be useful while rotating the image later. On line 10, the sprite instance self.ballSprite is created. Later, we will be setting the width and height of the Pyglet window as twice of the sprite width and thrice of the sprite height. The position of the image within the window is set on line 11. The initial position is chosen as shown in the next screenshot. In this case, there is only one Sprite instance. However, to make the program more general, a list of drawable objects called self.drawableObjects is maintained.

  5. To continue the discussion from the previous step, we will now review the on_draw method.
    def on_draw(self):
    self.clear()
    for d in self.drawableObjects:
    d.draw()

    As mentioned previously, the on_draw function is an API method of class pyglet.window.Window that is called when a window needs to be redrawn. This method is overridden here. The self.clear() call clears the previously drawn contents within the window. Then, all the Sprite objects in the list self.drawableObjects are drawn in the for loop.

    Python Multimedia: Animations Examples using Pyglet

  6. The preceding image illustrates the initial ball position in the animation.

  7. The method adjustWindowSize sets the width and height parameters of the Pyglet window. The code is self-explanatory:

    def adjustWindowSize(self):
    w = self.ballSprite.width * 3
    h = self.ballSprite.height * 3
    self.width = w
    self.height = h

  8. So far, we have set up everything for the animation to play. Now comes the fun part. We will change the position of the sprite representing the image to achieve the animation effect. During the animation, the image will also be rotated, to give it the natural feel of a bouncing ball.

    1 def moveObjects(self, t):
    2 if self.ballSprite.y - 100 < 0:
    3 self.rising = True
    4 elif self.ballSprite.y > self.ballSprite.height*2 - 50:
    5 self.rising = False
    6
    7 if not self.rising:
    8 self.ballSprite.y -= 5
    9 self.ballSprite.rotation -= 6
    10 else:
    11 self.ballSprite.y += 5
    12 self.ballSprite.rotation += 5

    This method is scheduled to be called 20 times per second using the following code in the program.

    pyglet.clock.schedule_interval(win.moveObjects, 1.0/20)

    To start with, the ball is placed near the top. The animation should be such that it gradually falls down, hits the bottom, and bounces back. After this, it continues its upward journey to hit a boundary somewhere near the top and again it begins its downward journey. The code block from lines 2 to 5 checks the current y position of self.ballSprite. If it has hit the upward limit, the flag self.rising is set to False. Likewise, when the lower limit is hit, the flag is set to True. The flag is then used by the next code snippet to increment or decrement the y position of self.ballSprite.

  9. The highlighted lines of code rotate the Sprite instance. The current rotation angle is incremented or decremented by the given value. This is the reason why we set the image anchors, anchor_x and anchor_y at the center of the image. The Sprite object honors these image anchors. If the anchors are not set this way, the ball will be seen wobbling in the resultant animation.
  10. Once all the pieces are in place, run the program from the command line as:

    $python SingleImageAnimation.py

    This will pop up a window that will play the bouncing ball animation. The next illustration shows some intermediate frames from the animation while the ball is falling down.

Python Multimedia: Animations Examples using Pyglet

What just happened?

We learned how to create an animation using just a single image. The image of a ball was represented by a sprite instance. This sprite was then translated and rotated on the screen to accomplish a bouncing ball animation. The whole functionality, including the event handling, was encapsulated in the class SingleImageAnimation.

LEAVE A REPLY

Please enter your comment!
Please enter your name here