13: Graphics and Animation

What We Will Cover


Continuations

Questions from last class?

Homework Questions?

What to take next?

13.1: Graphical Programming

Learner Outcomes

At the end of the lesson the student will be able to:

  • Write simple graphics programs containing points, lines, circles and text
  • Select appropriate coordinate systems for graphics
  • Process user input and mouse clicks in graphic programs

13.1.1: Introduction to Graphics

  • So far our programs have all worked from the console using cin and cout
  • In this section we will look at how to add graphics to a C++ program
  • To create a graphical program, we must use a graphics library
  • C++ does not have a standard graphics library
  • Thus we must always use a third-party graphics library

    Third-party library: a code library from someone other than the C++ provider (first party) and the user of C++ (second party)

  • The graphics library we will use is named Simple and Fast Multimedia Library (SFML)
  • SFML works on multiple platforms including Linux, OS-X and Windows
  • It is organized into five modules as shown below

SFML Modules

Module Description
System general utilities such as vector classes, conversion functions and timing classes
Window provides windows, events and input handling
Graphics 2D graphics library for drawing shapes, text and sprites
Audio classes for playing sounds, streaming music, recording and spatialization
Network utilities for networking with sockets and higher level protocols such as HTP and FTP

Introduction to Code::Blocks

  • To work with SFML at the command line would require lengthy commands
  • To make working with SFML easier, we are going to use an IDE named Code::Blocks
  • An IDE is like a text editor with added features
  • Code::Blocks runs on the major computing platforms including Linux, OS-X and Windows
  • In addition, Code::Blocks is configurable for use with different compilers including GCC/G++ using MinGW

Windows Installation

  • Installation is a two-step process:
    1. Install Code::Blocks
    2. Install SFML in Code::Blocks
  • Your instructor prepared detailed instructions in our How To's library
  • Unless you have installed Code::Blocks, you should use the school computers for this lesson instead of your own computer

Extra Credit Opportunity: earn up to five (5) extra credit points by emailing the instructor your written description of problems in the instructions and how to correct the problem. Make sure to include your name, the instruction title, the section and step number of the problem along with a description of the problem and the solution you propose. Each problem and its solution is worth varying amounts of points depending on how hard it was to solve. The first student to email the problem and fix gets the extra credit.

More Information

13.1.2: Starting a Graphical Project

  • Here is an SFML project to get us started on Windows: start-sfml
    • Right-click and select "Save Link As..."
    • This project assumes the same setup as the instructions and school PCs
    • If your setup is different, you will need to start a new SFML project
  • We download to our project folder and unzip the archive file
  • Once unzipped, we extract the file and double-click the file: start-sfml.cbp
  • The CBP (Code::Blocks Project) contains the information to start an exisiting project in Code::Blocks

Creating Graphical Windows

  • Before we can display graphical shapes we must first create a window
  • Rather than terminal windows, we will use a graphical window
  • To work with graphical windows, we need to include the SFML Graphics library
    #include <SFML/Graphics.hpp>
    
  • The .hpp in the above include is a C++ version of .h (header) files
  • We create a window in SFML using the sf::RenderWindow class
  • To create a window, we declare a RenderWindow object
    sf::RenderWindow win(sf::VideoMode(600, 400), "Hello SFML!");
    
  • The first argument is the VideoMode, which defines the width and height of the window
  • The second argument is the text to display on the title bar
  • The following is a minimal window example
  • If we run the code, the window will flash up briefly and then close, leaving only the terminal window

Minimal SFML Window Code

#include <SFML/Graphics.hpp>

int main()
{
    sf::RenderWindow win(sf::VideoMode(800, 600), "Hello SFML!");
    // more code here...

    return 0;
}

SFML Namespace

  • Notice that we are prepending the Window object name with sf::
  • All SFML modules are in the sf namespace
  • Thus we could add the following to the top of our program:
    using namespace sf;
    
  • However, then we could not use names like Window in our own code
  • Therefore we are better off just adding sf:: to the front of SFML named items such as classes

More Information

Check Yourself

  1. The data type of the following class declaration is ________.
    sf::RenderWindow win(sf::VideoMode(800, 600), "Hello SFML!");
    
  2. The width of the window in the above code is ________ pixels.
  3. The namespace used by SFML is ________.

13.1.3: Windows and Events

  • The previous example was short lived because the window closed immediately
  • For a more useful window, we need to add a main loop
    while (condition is true)
    {
        // more code here...
    ]
    
  • To better control the window, we need to discuss events

Event-Driven Programming

  • SFML uses a programming model known as Event-Driven Programming
  • A program waits for "events" to occur and then responds
  • Examples of events include:
    • Closed: the window requested to be closed
    • KeyPressed: a key was pressed
    • MouseButtonPressed: a mouse button was pressed
  • Code that responds to an event is known as event handler code
  • To respond to events, we check for events in our main loop
  • To check for events we call the Window class function pollEvent()
  • Events are queued in a list as more than one event may occur since our last check
  • Thus we always call pollEvent() in a loop:
    sf::Event event; // declare event variable
    while (win.pollEvent(event)) // request next event
    {
        // Check for event types
        if (event.type == sf::Event::Closed)
        {
            win.close();
        }
        // check other event types...
    }
    
  • The following example shows our window code with event handling added

SFML Window with Event Handling

#include <SFML/Graphics.hpp>

int main()
{
    sf::RenderWindow win(sf::VideoMode(800, 600), "Hello SFML!");

    // Run the program as long as the window is open
    while (win.isOpen())
    {
        // Check all the window's events that were triggered
        // since the last iteration of the main loop.
        sf::Event event;
        while (win.pollEvent(event))
        {
            // Close event so we close the window
            if (event.type == sf::Event::Closed)
            {
                win.close();
            }
            // Escape key pressed
            if ((event.type == sf::Event::KeyPressed)
                && (event.key.code == sf::Keyboard::Escape))
            {
                win.close();
            }
        }
    }

    return 0;
}

More Information

Try It: Start an SFML Project in Code::Blocks (5m)

  1. Start an SFML project in Code::Blocks by:
    1. Right-clicking start-sfml, selecting "Save Link As...", and saving the link to a convenient location like the Desktop.
    2. Unzip the file and open the folder named start-sfml.
    3. Double-click the file start-sfml.cbp to open Code::Blocks.

    For more information see lesson 13.1.2: Starting a Graphical Project. If your setup is different than the school computers, you will need to start a new SFML project

  2. Verify that the main.cpp window contains the same code as SFML Window with Event Handling program from above.
  3. Build the project by clicking on the Build icon Code::Blocks build icon in the toolbar, using the menus Build->Build, or pressing Ctrl+F9.

    Code::Blocks Build icon

  4. When you build, you will see a pane at the bottom of Code::Blocks called Build Log. You should see an output like this showing the compiler did not report any errors:

    If you have problems, ask a classmate or the instructor for help as needed.

Code::Blocks Build Log

  1. Run the program by clicking on the Run icon Code::Blocks run icon in the toolbar, using the menus Build->Run, or pressing Ctrl+F10, and verify you see the following graphics window.

    If you have problems, ask a classmate or the instructor for help.

    Verify test graphics

  2. After closing the graphics window by pressing the red X, verify the message process returned 0 appears in the console.

    Verify correct ending

  3. Click on the console and press the space key to close the console window.

    Congratulations! You can now start an SFML graphics project in Code::Blocks.

  4. When finished please help those around you.

Check Yourself

  1. True or false: we should always add a main loop to our SFML code.
  2. In the following event loop code, the event information is provided in the ________
    sf::Event event;
    while (win.pollEvent(event))
    {
        if (event.type == sf::Event::Closed)
        {
            win.close();
        }
    }
    
    1. Event class
    2. event object
    3. return type of the pollEvent() function
    4. Cannot tell from the information given
  3. True or false: the pollEvent() function has a reference parameter.

13.1.4: Keyboard and Mouse Inputs

  • We cannot use cin to get input from a graphics window
  • cin gets input from the console and not the graphics window
  • To find out if a keyboard key is pressed, we use the sf::Keyboard class
  • The class has one function:
    static bool isKeyPressed(Key key)
    
  • The keyword static means that we do not have to construct an object to call the function
  • To call the function we write code like:
    if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left))
    {
        // move left...
    }
    else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right))
    {
        // move right...
    }
    else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Escape))
    {
        // quit...
    }
    
  • The keyboard is queried in real time whether or not the window has focus
  • If we want to read keys only when the window is in focus, we check for events instead:
    while (win.pollEvent(event))
    {
        // Escape key pressed
        if ((event.type == sf::Event::KeyPressed)
            && (event.key.code == sf::Keyboard::Escape))
        {
            win.close();
        }
    }
    
  • A list of the keyboard codes is included in the sf::Keyboard Class Reference

Mouse Input

  • A mouse is more complicated than a keyboard, with both buttons and (x, y) position
  • To find out information about the mouse, we use the sf::Mouse class
  • The sf::Mouse class has five functions as described in the table below
  • To determine is a mouse button is pressed we write code like:
    if (sf::Mouse::isButtonPressed(sf::Mouse::Left))
    {
        sf::Vector2i pos = sf::Mouse::getPosition(win);
        std::cout << pos.x << " " << pos.y << "\n";
    }
    
  • We can see the mouse button types in the sf::Mouse Class Reference below
  • The following program demonstrates how to get keyboard and mouse input

sf::Mouse Class Functions

Function Description
isButtonPressed(button) Returns true if a specified mouse button is pressed.
getPosition() Returns the (x, y) position of the mouse in desktop coordinates.
getPosition(window) Returns the (x, y) position of the mouse within the given window.
setPosition(Vector2i) Sets the (x, y) position of the mouse in desktop coordinates.
setPosition(Vector2i, window) Sets the (x, y) position of the mouse in window coordinates.

Program click.cpp: Keyboard and Mouse Input

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
#include <SFML/Graphics.hpp>

int main()
{
    const float SIZE = 400.0f;
    sf::RenderWindow win(sf::VideoMode(SIZE, SIZE), "Test");

    while (win.isOpen())
    {
        // Process events
        sf::Event event;
        while (win.pollEvent(event))
        {
            if (event.type == sf::Event::Closed)
            {
                win.close();
            }
            // Escape key pressed
            if ((event.type == sf::Event::KeyPressed)
                && (event.key.code == sf::Keyboard::Escape))
            {
                win.close();
            }
        }
        if (sf::Mouse::isButtonPressed(sf::Mouse::Left))
        {
            sf::Vector2i pos = sf::Mouse::getPosition(win);
            std::cout << pos.x << " " << pos.y << "\n";
        }
    }

    return 0;
}

Try It: Explore Mouse Clicks (3m)

  1. Open your CodeBlocks project from the last Try It.
  2. Copy the above code into your main.cpp file, replacing the original code.
  3. Build and run the new project to make sure you copied the code correctly.

    If you have problems, ask a classmate or the instructor for help as needed.

  4. While the project is running, click your mouse at different locations on your screen and observe the (x, y) coordinates displayed.
  5. When finished, please help those around you. Then be prepared to answer the following Check Yourself questions when called upon.

Check Yourself

  1. The size of an SFML window is measured in ________.
  2. True or false: the coordinate system used by SFML is the same as that used in algebra.
  3. The origin (0, 0) of the (x, y) coordinate system is located at the ________.
    1. upper left-hand corner of the window
    2. upper right-hand corner of the window
    3. lower right-hand corner of the window
    4. lower left-hand corner of the window
  4. True or false: in the coordinate system used by SFML, locations shown in the window are always positive numbers.

More Information

13.1.5: Creating Graphical Shapes

  • In this section we discuss how to display graphical shapes
  • SFML has several built-in shape objects including circles and rectangles
  • Drawing shapes is a two step process:
    1. Create the shape object
    2. Render (draw) the shape object
  • We usually construct the shape objects before the event loop
  • Then we draw the shapes inside the event loop

Creating Circles

  • To create a circular shape we declare an sf::CircleShape object with a radius
  • For example:
    sf::CircleShape circ(30.0f);
    
  • The radius is a float type so we include the "f" after the number

Creating Rectangles

  • To create a rectangular shape we declare an sf::RectangleShape with a width and height
  • For example:
    sf::RectangleShape rec(sf::Vector2f(120, 50));
    
  • Notice the argument sf::Vector2f(120, 50)
  • Inside the parenthesis, the first value is the width and the second is the height
  • An sf::Vector2 is utility class that holds two data values
  • In this case we are using an sf::Vector2f, which holds two float data values
  • Other common types include:
    • sf::Vector2i: holds two int values
    • sf::Vector2u: holds two unsigned int values

Creating Drawable Images

  • To create a drawable image we need both a Texture and a Sprite
  • An sf::Texture is an image that can be drawn but does not have a position
  • Since a texture does not have a position, it needs help from a class like sf::Sprite
  • An sf::Sprite takes a texture and can be positioned on a screen
  • Thus creating a drawable image is a multi-step process like:
    // Need texture and sprite to display an image
    sf::Texture tex;
    if (!tex.loadFromFile("soccer.png")) return -1;
    sf::Sprite ball;
    ball.setTexture(tex);
    
  • For example, we could draw the following image:

    soccer ball image

  • We save the image in our working project folder

Drawing Shapes

  • Once we have defined a shape, we draw the shape using draw function
  • For example, to draw a circle shape named circ and a rectangle named rec:
    win.clear(); // first step
    win.draw(circ);
    win.draw(rec);
    win.draw(ball);
    win.display(); // last step
    
  • Notice that before we draw shapes we always call the window function clear()
  • After drawing shapes, we call the window function display()

More Information

  • sf::CircleShape: SFML class documentation for circle shapes.
  • sf::RectangleShape: SFML class documentation for rectangle shapes.
  • sf::ConvexShape: SFML class documentation for drawing a convex polygon.
  • sf::Shape: lists the common shape functions
  • sf::Sprite: SFML class documentation for creating a drawable texture.
  • sf::Text: SFML class documentation for drawing text
  • sf::Texture: SFML class documentation for drawing images stored in the graphics card memory.

Check Yourself

  1. Enter the code to create a circular shape object with a radius of 42 pixels.

    one answer

  2. Enter the code to create a rectangular shape object with a width of 42 pixels and a height of 24 pixels.

    one answer

  3. True or false: An sf::Vector2f holds two values of type double.
  4. Before calling one or more draw() functions, always call ________ function of the window object.
  5. To display graphical shapes, call the ________ function of the window object after calling one or more draw() functions.

13.1.6: Shape Transformations

  • We can transform (change) a shape by calling member functions of the shape object
  • The are several transformation functions common to all shapes as described below

Some Commonly Used Shape Transformation Functions

Function Description
move(dx, dy) Moves the object by (dx, dy) relative to the current position.
rotate(angle) Add angle to the current rotation in degrees.
scale(x, y) Change the size of (scale) the object by multiplying the (x, y) dimensions.
setFillColor() Set the fill color of the shape.
setPosition(x, y) Set the absolute position of the object from the upper-left corner of the window.
setRotation(angle) Set the absolute orientation of the object in degrees.

Positioning a Shape

  • A window is organized into an (x, y) coordinate system
  • To set the location of a shape, we set its (x, y) coordinates

Window coordinates

  • Notice that the upper left corner is the origin (0, 0)
  • The x-coordinate is always positive and increases to the right
  • The y-coordinate is always positive and increases in the downward direction
  • Each shape is drawn from its upper left corner as well
  • We can see examples of positioning and other transformations in the following example

Example Using Shape Transformation Functions

#include <SFML/Graphics.hpp>

int main()
{
    const float SIZE = 60.0f;
    sf::RenderWindow win(sf::VideoMode(600, 400), "Transforms");

    // Declare shape objects
    sf::CircleShape circ(SIZE / 2);
    sf::RectangleShape rec(sf::Vector2f(SIZE, SIZE / 2));
    sf::RectangleShape line(sf::Vector2f(SIZE, 1));
    // Need texture and sprite to display an image
    sf::Texture tex;
    if (!tex.loadFromFile("soccer.png")) return -1;
    sf::Sprite ball;
    ball.setTexture(tex);

    // Transform the shapes
    circ.setFillColor(sf::Color::Green);
    rec.move(0, SIZE);
    line.scale(2, 1);
    line.rotate(45);
    ball.move(80, 80);

    while (win.isOpen())
    {
        // Process events
        sf::Event event;
        while (win.pollEvent(event))
        {
            if (event.type == sf::Event::Closed)
            {
                win.close();
            }
        }
        // Drawing sequence
        win.clear(); // first step
        win.draw(circ);
        win.draw(rec);
        win.draw(line);
        win.draw(ball);
        win.display(); // last step
    }

    return 0;
}

More Information

  • sf::Shape: lists the common shape transformation functions

Try It: Drawing and Transforming Shapes (4m)

  1. Open your CodeBlocks project from the last Try It.
  2. Copy the above code into your main.cpp file, replacing the original code.
  3. Build and run the new project to make sure you copied the code correctly.

    If you have problems, ask a classmate or the instructor for help as needed.

  4. Observe the screen and find the code that displayed each shape.
  5. When finished, please help those around you. Then be prepared to answer the following Check Yourself questions when called upon.

Check Yourself

  1. Of the following, the class that is NOT a built-in SFML shape is ________.
    1. CircleShape
    2. ConvexShape
    3. LineShape
    4. RectangleShape
  2. Given the following object, enter a statement to move rec 4 pixels in the x direction and 2 pixels in the y direction.
    sf::RectangleShape rec(sf::Vector2f(42, 24));
    

    answer

  3. Enter a statement to rotate the above rec object by 90 degrees.

    answer

13.1.7: Colors

  • One of the commonly used shape transformations is color
  • To change the color of shapes we need to learn how to make and use colors

Creating Colors

  • Most displays use a color model known as RGB (Red, Green, Blue)
  • The red, green and blue values combine to create the overall color:

    RGB colors

  • SFML Supports the standard RGBA color model with the sf::Color class
  • We create colors by specifying red, green and blue (RGB) values
  • RGB values are specified using int values from 0 to 255:
    sf::Color myColor(int r, int g, int b)  // 0 - 255
    
  • For example:
    sf::Color chocolate(204, 102, 0);
    
  • To help choosing colors, use this color chart of (r, g, b) values
  • In addition, the sf::Color class has several color constants that you can use in place of numbers as shown below
  • In SFML, we make use of colors when we want to fill shapes
    sf::CircleShape circ(42.0f);
    circ.setFillColor(sf::Color::Magenta)
    

Some Color Constants

Color Constant Red, Green, Blue
sf::Color::Red 255, 0, 0
sf::Color::Blue 0, 0, 255
sf::Color::Yellow 255, 255, 0
sf::Color::Black 0, 0, 0
Color Constant Red, Green, Blue
sf::Color::Green 0, 255, 0
sf::Color::Magenta 255, 0, 255
sf::Color::Cyan 0, 255, 255
sf::Color::White 255, 255, 255

Transparency

  • Every pixel on a screen has two components: color and transparency
  • The transparency component of color is referred to as the alpha value
  • By adjusting the alpha value, background colors show through
  • We can see this effect in the following image
  • The color is less intense and the background checkerboard image becomes more visible as the alpha value decreases

    Effect of transparency

Creating Transparent Colors

  • To add transparency to a color, we add an optional fourth argument when creating a color
    sf::Color myColor(int r, int g, int b, int a)  // 0 - 255
    
  • For example:
    sf::Color myColor(146, 223, 227, 128);
    
  • Alpha values are specified using int values from 0 to 255
  • An alpha value of 0 is completely transparent (invisible) whereas 255 is completely opaque (solid)
  • Values in between 0 and 255 are translucent -- some background shows through
  • Note that sf::Color has a color const of Transparent
    sf::Color::Transparent
    

Check Yourself

  1. To set the color of a shape, call the function ________.
  2. The three color components are: R__________, G__________, B__________.
  3. Write the code codes to construct the following colors in SFML:
    sf::Color red(); answer
    sf::Color green(); answer
    sf::Color blue(); answer
    sf::Color cyan(); answer
    
  4. What are two ways to complete the following statement? (answer)
    sf::Color magenta =
  5. Look at the Color Chart of RGB Triplets and fill in the codes to construct the following colors:
    sf::Color orange();
    sf::Color purple();
    
  6. The alpha value for completely transparent is ________.
  7. The alpha value for completely opaque (solid) is ________.

  8. answer

More Information

Exercise 13.1: Drawing on Our Programming Knowledge! (10m)

In this exercise we look at how to draw simple graphic shapes. Specifically, you will write a program to display a drawing like the following:

Face picture

Use this code to get started:

#include <SFML/Graphics.hpp>

int main()
{
    const float SIZE = 400.0f;
    sf::RenderWindow win(sf::VideoMode(SIZE, SIZE), "Face");

    // Create the head

    // Create and position the eyes

    // Create and position the mouth

    // Create and position the nose

    while (win.isOpen())
    {
        // Process events
        sf::Event event;
        while (win.pollEvent(event))
        {
            if (event.type == sf::Event::Closed)
            {
                win.close();
            }
        }
        // Drawing sequence
        win.clear();
        // add drawing commands here
        win.display();
    }

    return 0;
}

Specifications

  1. Open your Code::Blocks project from the last Try It.

    If you have not already created the project, complete the Try It: Start an SFML Project in Code::Blocks exercise at a minimum.

  2. Copy the starter code from above into the main.cpp file and then compile and run the starter program to make sure you copied it correctly.

    If you have problems, ask a classmate or the instructor for help before continuing.

  3. In main(), create an sf::CircleShape for the "head" of the "face" using code like:
    sf::CircleShape head(SIZE / 2);
    head.setFillColor(sf::Color::White);
    

    A CircleShape object requires a radius. For more information, see section: 13.1.5: Creating Graphical Shapes.

  4. In the drawing section, add code to draw the head like this:
    win.draw(head);
    
  5. Build and run your project to verify you see a white circle representing the head of your face.

    If you have problems, ask a classmate or the instructor for help before continuing.

  6. Create and draw two eyes, like you did for the head, then add color to the eyes and position the eyes on the head.

    The size of the eyes is around 20 pixels. For information on color, see section 13.1.7: Colors. Use code like the following to position the eyes:

    leftEye.setPosition(sf::Vector2f(100, 120));
    rightEye.setPosition(sf::Vector2f(260, 120));
    

    The above code only positions the eyes, so you need to add the object construction and draw() commands. Build and run the project to verify your code is correct. If you have problems, ask a classmate or the instructor for help before continuing.

  7. Next, add code to create and draw a line for the mouth using code like the following:
    sf::RectangleShape mouth(sf::Vector2f(SIZE / 2, 2));
    mouth.setFillColor(sf::Color::Red);
    mouth.setPosition(sf::Vector2f(100, 280));
    

    A line is simply a thin rectangle. For more information, see section: 13.1.5: Creating Graphical Shapes.

  8. Place the following image into the working directory, saving the image as soccer.png:

    soccer ball image

  9. Now add code to create and draw the nose image using a texture and a sprite like:
    sf::Texture tex;
    if (!tex.loadFromFile("soccer.png")) return -1;
    sf::Sprite nose;
    nose.setTexture(tex);
    sf::Vector2u imgSize = tex.getSize();
    int x = (SIZE - imgSize.x) / 2;
    int y = (SIZE - imgSize.y) / 2;
    nose.setPosition(sf::Vector2f(x, y));
    
  10. Compile and execute your code to verify you see something like the image above.

    Do not forget the draw() commands in the drawing sequence towards the end of main(). For more information, see the Drawing Shapes section in 13.1.5: Creating Graphical Shapes.

  11. Add code to the main loop to quit the program when the escape (Esc) key is pressed.

    For more information, see section: 13.1.4: Keyboard and Mouse Inputs.

  12. Add code to the main loop to display the mouse coordinates when the left mouse button is pressed.

    For more information, see section: 13.1.4: Keyboard and Mouse Inputs.

  13. If you have difficulty, verify your code against that shown here: face.cpp.

13.2: Computer Animation

Objectives

At the end of the lesson the student will be able to:

  • Explain the principles of animation
  • Create code for simple animation
  • Write code for animation objects

13.2.1: Introduction to Computer Animation

  • Animation is the illusion of motion created by displaying a series of images or shapes
  • For example, the following animation displays at 10 frames per second (FPS):

    example animation

  • The speed of the display is fast enough that you cannot easily see the individual frames
  • Contrast this with the following image that displays at 2 frames per second:

    example animation at slower frame rate

  • At 2 FPS, the animation is slow enough that you can see the individual frames
  • Both of these animations were produced by displaying these images, known as frames:

    animation frames

  • The speed with which the frames are displayed is known as the framerate
  • Note that these images are in the public domain and were obtained from Wikipedia

Animation Loop

  • To create movement over time, we use an animation loop
  • There are three steps to an animation loop as shown in the following diagram:

    update,render,wait

  • During the update portion of the loop, the computer calculates the position of each shape
  • During the render portion, the computer draws our shapes
  • Then the computer waits (pauses) a short while before repeating the process
  • There are two reasons for waiting:
    1. To slow down the animation's frame rate
    2. To allow other programs on the computer to run
  • The second reason is important but not always obvious
  • A modern operating system runs several programs at the same time
  • At some point, other programs need a chance to run
  • A good time for other programs to run is when our program does not need to run

Check Yourself

  1. Animation is the ________ of motion created by displaying a series of images or shapes.
  2. True or false: if the animation is too fast, it appears jerky.
  3. Each image in an animation is sometimes called a ________.
  4. True or false: one reason for pausing during an animation is to allow other programs on the computer time to run.

13.2.2: Animating Shapes

  • Here is the SFML project we used in the last section: start-sfml
    • Right-click and select "Save Link As..."
    • This project assumes the same setup as the instructions and school PCs
    • If your setup is different, you will need to start a new SFML project
  • Recall that an animation loop has three steps:
    1. Update
    2. Render
    3. Sleep
  • During the update phase of the loop our code calculates the position of our objects
  • For example, if we had an sf::CircleShape named circ we would code:
    circ.move(dx, dy);
    
  • During the render phase we:
    1. Start with a call to the window clear() function
      win.clear()
    2. Then we call the draw() function of each object
      win.draw(circ);
    3. We end the render phase with a call to the window display() function
      win.display()
  • When running an animation, we may get visual artifacts such as screen tearing
  • Screen tearing is a visual anomaly where a display device shows information from two or more video frames in a single screen draw
  • To reduce these visual artifacts, we set the vertical synchronization to match our monitor
    win.setVerticalSyncEnabled(true);
    
  • After this function call, our application runs at the same frequency as the monitor's refresh rate
  • SFML then automatically pauses after each display
  • Most LCD monitors run at 60 frames per second
  • For more information see: Controlling the framerate

Example Animation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <SFML/Graphics.hpp>

int main()
{
    sf::RenderWindow win(sf::VideoMode(600, 400), "Anim1");
    win.setVerticalSyncEnabled(true);

    // Setup
    const float RADIUS = 30.f;
    sf::CircleShape circ(RADIUS);
    circ.setFillColor(sf::Color::Green);
    float dx = 2.5f;
    float dy = 0.0f;

    // Animation loop
    while (win.isOpen())
    {
        // Event polling to control window closing
        sf::Event event;
        while (win.pollEvent(event))
        {
            if (event.type == sf::Event::Closed)
            {
                win.close();
            }
        }
        // Update
        circ.move(dx, dy);
        // Render
        win.clear();
        win.draw(circ);
        win.display(); // start sleep part of cycle
    }

    return 0;
}

More Information

13.2.3: Bouncing off the Walls

  • To make our animation more interesting, we can add a bounce to the circle
  • Imagine the circle is a ball and that we want the ball to bounce off the walls
  • Imitating a real thing or process is known as a simulation
  • One use of computers is simulating, or modeling, key characteristic of systems
  • For our simulation, we use the sides, top and bottom of the window as the walls
  • Remember that our coordinate system starts in the window at (0, 0)
  • The x-coordinate is always positive and increases to the right
  • The y-coordinate is always positive and increases in the downward direction

Window coordinates

Finding the Walls

  • We know that when x == 0 our shape is at the left side of the window
  • Similarly, when y == 0 our shape is at the top of the window
  • We can find the size of the window by calling the window getSize() function
    sf::Vector2u size = win.getSize();
    
  • The width == size.x and the height == size.y
  • We can find the position of our ball by calling the getPosition() function
    sf::Vector2f pos = circ.getPosition();
    
  • The left side of the ball is at pos.x and the top is at pos.y
  • To test if the ball exceeds the boundaries in the x-direction, we use an if-else statement like:
    if (pos.x < 0)
    {
        dx = SPEED_X;
    }
    else if (pos.x + RADIUS * 2 > size.x)
    {
        dx = -SPEED_X;
    }
    
  • If the ball exceeds the boundaries we reverse its direction
  • Similarly, we test if the ball exceeds the top and bottom boundaries with:
    if (pos.y < 0)
    {
        dy = SPEED_Y;
    }
    else if (pos.y + RADIUS * 2 > size.y)
    {
        dy = -SPEED_Y;
    }
    
  • The bounce code goes in the update portion of the animation loop as shown in the following example

Example Animation With Bounce

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <cmath>
#include <SFML/Graphics.hpp>

int main()
{
    sf::RenderWindow win(sf::VideoMode(600, 400), "Anim2");
    win.setVerticalSyncEnabled(true);
    const float RADIUS = 30.f;
    const float SPEED_X = 2.5f;
    const float SPEED_Y = 2.5f;
    sf::CircleShape circ(RADIUS);
    circ.setFillColor(sf::Color::Green);
    float dx = SPEED_X;
    float dy = SPEED_Y;

    while (win.isOpen())
    {
        sf::Event event;
        while (win.pollEvent(event))
        {
            if (event.type == sf::Event::Closed)
            {
                win.close();
            }
        }
        // Update
        sf::Vector2u size = win.getSize();
        sf::Vector2f pos = circ.getPosition();
        if (pos.x < 0)
        {
            dx = std::abs(dx);
        }
        else if (pos.x + RADIUS * 2 > size.x)
        {
            dx = -std::abs(dx);
        }
        if (pos.y < 0)
        {
            dy = std::abs(dy);
        }
        else if (pos.y + RADIUS * 2 > size.y)
        {
            dy = -std::abs(dy);
        }
        circ.move(dx, dy);
        // Render
        win.clear();
        win.draw(circ);
        win.display();
    }

    return 0;
}

13.2.4: Adding a Bouncing Sound

  • We can add a sound to our animation
  • We will use the following example sound for our animation

    Bounce sound: bounce.wav

  • We place the sound file in our project folder
  • To play sounds we must include the audio library
    #include <iostream>
    #include <SFML/Audio.hpp>
    
  • The iostream library is for error messages
  • To play a sound we need both a SoundBuffer and a Sound object
    // Declare a new sound buffer
    sf::SoundBuffer buffer;
    
  • A SoundBuffer is like a texture in an image
  • We load the data from a file into a SoundBuffer and check for error
    // Load it from a file
    if (!buffer.loadFromFile("bounce.wav"))
    {
        std::cout << "Error loading sound file" << std::endl;
        return -1;
    }
    
  • The SoundBuffer holds the data of a sound
  • To play the sound, we need to move the sound data to a speaker
    // Create a sound source and bind it to the buffer
    sf::Sound bounce;
    bounce.setBuffer(buffer);
    
  • The Sound class provides functions to play, pause and stop the the sound
    // Play the sound
    bounce.play();
    
  • In the following example, we play the "bounce" sound whenever the ball bounces off the side of the window

Example Bounce with Sound

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include <cmath> // for abs()
#include <cstdlib> // for exit()
#include <iostream>
#include <SFML/Audio.hpp>
#include <SFML/Graphics.hpp>

int main()
{
    sf::RenderWindow win(sf::VideoMode(600, 400), "Anim2");
    win.setVerticalSyncEnabled(true);
    const float RADIUS = 30.f;
    const float SPEED_X = 2.5f;
    const float SPEED_Y = 2.5f;
    sf::CircleShape circ(RADIUS);
    circ.setFillColor(sf::Color::Green);
    float dx = SPEED_X;
    float dy = SPEED_Y;
    sf::SoundBuffer buffer;
    if (!buffer.loadFromFile("bounce.wav"))
    {
        std::cout << "Error opening sound file.\n";
        exit(-1);
    }
    sf::Sound bounce;
    bounce.setBuffer(buffer);

    while (win.isOpen())
    {
        sf::Event event;
        while (win.pollEvent(event))
        {
            if (event.type == sf::Event::Closed)
            {
                win.close();
            }
        }
        // Update
        sf::Vector2u size = win.getSize();
        sf::Vector2f pos = circ.getPosition();
        if (pos.x < 0)
        {
            dx = std::abs(dx);
            bounce.play();
        }
        else if (pos.x + RADIUS * 2 > size.x)
        {
            dx = -std::abs(dx);
            bounce.play();
        }
        if (pos.y < 0)
        {
            dy = std::abs(dy);
            bounce.play();
        }
        else if (pos.y + RADIUS * 2 > size.y)
        {
            dy = -std::abs(dy);
            bounce.play();
        }
        circ.move(dx, dy);
        // Render
        win.clear();
        win.draw(circ);
        win.display();
    }

    return 0;
}

More Information

13.2.5: Animating Two Objects

  • If we want to animate two shapes, we need separate variables for each object
  • As we add more shape objects, our code in the animation loop becomes more cluttered
  • To avoid the clutter and duplication, we can encapsulate the code for the shape in a class
  • Here is a Ball class that encapsulates the information for the moving shape
  • We add a new file to Code::Blocks with File > New > Empty File
  • Following the Ball class is an animation application bouncing two balls
  • Notice how simple the animation loop remains

Ball Class Header File (ball.h)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <SFML/Graphics.hpp>
#ifndef BALL_H
#define BALL_H

const float RADIUS = 30.f;

class Ball
{
public:
    Ball();
    Ball(float x, float y, float speed, sf::Color ballColor);
    void update(sf::RenderWindow& win);
    void draw(sf::RenderWindow& win);
private:
    sf::CircleShape circ;
    float dx, dy;
};
#endif

Ball Class Implementation File (ball.cpp)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <cmath>
#include "ball.h"

Ball::Ball()
{
    const float SPEED = 2.5f;
    circ = sf::CircleShape(RADIUS);
    circ.setFillColor(sf::Color::Green);
    dx = SPEED;
    dy = SPEED;
}

Ball::Ball(float x, float y, float speed, sf::Color ballColor)
{
    circ = sf::CircleShape(RADIUS);
    circ.setFillColor(ballColor);
    circ.setPosition(x, y);
    dx = speed;
    dy = speed;
}

void Ball::update(sf::RenderWindow& win)
{
    sf::Vector2u winSize = win.getSize();
    sf::Vector2f pos = circ.getPosition();
    if (pos.x < 0)
    {
        dx = std::abs(dx);
    }
    else if (pos.x + RADIUS * 2 > winSize.x)
    {
        dx = -std::abs(dx);
    }
    if (pos.y < 0)
    {
        dy = std::abs(dy);
    }
    else if (pos.y + RADIUS * 2 > winSize.y)
    {
        dy = -std::abs(dy);
    }
    circ.move(dx, dy);
}

void Ball::draw(sf::RenderWindow& win)
{
    win.draw(circ);
}

Example Animation With Two Balls (main.cpp)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <SFML/Graphics.hpp>
#include "ball.h"

int main()
{
    sf::RenderWindow win(sf::VideoMode(600, 400), "Anim3");
    win.setVerticalSyncEnabled(true);

    // Random speed
    srand(time(0));
    float speed = 2.0f + ((float) rand() / RAND_MAX);
    Ball b1 = Ball(1, 1, speed, sf::Color::Red);
    speed = 2.0f + ((float) rand() / RAND_MAX);
    Ball b2 = Ball(100, 1, speed, sf::Color::Green);

    while (win.isOpen())
    {
        sf::Event event;
        while (win.pollEvent(event))
        {
            if (event.type == sf::Event::Closed)
            {
                win.close();
            }
        }
        // Update
        b1.update(win);
        b2.update(win);
        // Render
        win.clear();
        b1.draw(win);
        b2.draw(win);
        win.display();
    }

    return 0;
}

13.2.6: Animating Many Objects

  • We can take our animation one step further and animate many objects
  • To juggle several balls at once we use a list such as a vector or array with a counting loop
  • We can see the loops with the vector in the following example

Example Animation With Many Balls

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <vector>
#include <SFML/Graphics.hpp>
#include "ball.h"

int main()
{
    srand(time(0));
    sf::RenderWindow win(sf::VideoMode(600, 400), "Anim4");
    win.setVerticalSyncEnabled(true);

    const int NUM_BALLS = 10;
    std::vector<Ball> balls(NUM_BALLS);
    const int NUM_COLORS = 6;
    const sf::Color colors[] =
    {
        sf::Color::Red, sf::Color::Green, sf::Color::Blue,
        sf::Color::Yellow, sf::Color::Magenta, sf::Color::Cyan
    };

    // Initialize vector of balls
    for (unsigned i = 0; i < balls.size(); i++)
    {
        float x = rand() % 550;
        float y = rand() % 350;
        float speed = 2.0f + ((float) rand() / RAND_MAX);
        balls[i] = Ball(x, y, speed, colors[i % NUM_COLORS]);
    }

    while (win.isOpen())
    {
        sf::Event event;
        while (win.pollEvent(event))
        {
            if (event.type == sf::Event::Closed)
            {
                win.close();
            }
        }
        // Update
        for (unsigned i = 0; i < balls.size(); i++)
        {
            balls[i].update(win);
        }
        // Render
        win.clear();
        for (unsigned i = 0; i < balls.size(); i++)
        {
            balls[i].draw(win);
        }
        win.display();
    }

    return 0;
}

Exercise 13.2: Simple Animation (5m)

In this exercise we compile and run the example application showing the animation of several shapes.

Specifications

  1. Start an SFML project in Code::Blocks by:
    1. Right-clicking start-sfml, selecting "Save Link As...", and saving the link to a convenient location like the Desktop.
    2. Unzip the file and open the folder named start-sfml.
    3. Double-click the start-sfml.cbp to open Code::Blocks.

    For more information see lesson 13.1.2: Starting a Graphical Project. If your setup is different than the school computers, you will need to start a new SFML project

  2. Add a new file named ball.h to the project with File > New > Empty Fileand copy the following code into the file:
    #include <SFML/Graphics.hpp>
    #ifndef BALL_H
    #define BALL_H
    
    const float RADIUS = 30.f;
    
    class Ball
    {
    public:
        Ball();
        Ball(float x, float y, float speed, sf::Color ballColor);
        void update(sf::RenderWindow& win);
        void draw(sf::RenderWindow& win);
    private:
        sf::CircleShape circ;
        float dx, dy;
    };
    #endif
    
  3. Add a new file named ball.cpp to the project with File > New > Empty File and copy the following code into the file:
    #include <cmath>
    #include "ball.h"
    
    Ball::Ball()
    {
        const float SPEED = 2.5f;
        circ = sf::CircleShape(RADIUS);
        circ.setFillColor(sf::Color::Green);
        dx = SPEED;
        dy = SPEED;
    }
    
    Ball::Ball(float x, float y, float speed, sf::Color ballColor)
    {
        circ = sf::CircleShape(RADIUS);
        circ.setFillColor(ballColor);
        circ.setPosition(x, y);
        dx = speed;
        dy = speed;
    }
    
    void Ball::update(sf::RenderWindow& win)
    {
        sf::Vector2u winSize = win.getSize();
        sf::Vector2f pos = circ.getPosition();
        if (pos.x < 0)
        {
            dx = std::abs(dx);
        }
        else if (pos.x + RADIUS * 2 > winSize.x)
        {
            dx = -std::abs(dx);
        }
        if (pos.y < 0)
        {
            dy = std::abs(dy);
        }
        else if (pos.y + RADIUS * 2 > winSize.y)
        {
            dy = -std::abs(dy);
        }
        circ.move(dx, dy);
    }
    
    void Ball::draw(sf::RenderWindow& win)
    {
        win.draw(circ);
    }
    
  4. Copy the following code into the main.cpp file:
    #include <vector>
    #include <SFML/Graphics.hpp>
    #include "ball.h"
    
    int main()
    {
        srand(time(0));
        sf::RenderWindow win(sf::VideoMode(600, 400), "Anim4");
        win.setVerticalSyncEnabled(true);
    
        const int NUM_BALLS = 10;
        std::vector<Ball> balls(NUM_BALLS);
        const int NUM_COLORS = 6;
        const sf::Color colors[] =
        {
            sf::Color::Red, sf::Color::Green, sf::Color::Blue,
            sf::Color::Yellow, sf::Color::Magenta, sf::Color::Cyan
        };
    
        // Initialize vector of balls
        for (unsigned i = 0; i < balls.size(); i++)
        {
            float x = rand() % 550;
            float y = rand() % 350;
            float speed = 2.0f + ((float) rand() / RAND_MAX);
            balls[i] = Ball(x, y, speed, colors[i % NUM_COLORS]);
        }
    
        while (win.isOpen())
        {
            sf::Event event;
            while (win.pollEvent(event))
            {
                if (event.type == sf::Event::Closed)
                {
                    win.close();
                }
            }
            // Update
            for (unsigned i = 0; i < balls.size(); i++)
            {
                balls[i].update(win);
            }
            // Render
            win.clear();
            for (unsigned i = 0; i < balls.size(); i++)
            {
                balls[i].draw(win);
            }
            win.display();
        }
    
        return 0;
    }
    
  5. Build and run your project to verify the animation works correctly.

    If you have problems ask a classmate or the instructor for help.

  6. Take a few minutes to experiment with the number of objects in the animation.
  7. There is no need to turn in this exercise.

Wrap Up

Due Next:
Sampler Project (5/10/17)
When class is over, please shut down your computer if it is on
Work on your project!
Last Updated: April 26 2017 @20:41:56