Schedule‎ > ‎

Lesson 13 Graphics

What to take next?


13.1: Separate Compilation

Objectives

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

  • Separate classes from applications
  • Discuss why programs should be separated into parts
  • Describe how to separate the interface from the implementation
  • Use separate compilation


13.1.1: Separating Classes from the main() Function

  • When we work with classes and objects, we usually specify a class in one file and write code to use the class in another file
  • This creates a more modular set of classes and allows you to reuse classes in other programs without copying and pasting code
  • Recall the #include directive:
    #include <iostream>
  • It turns out we can include our own files into other program files
  • Syntax:
    #include "myfile.cpp"
  • For example:
    #include "product.cpp"

Programs and the main() Function

  • In C++, each program can have only one main() function
  • Usually, the main() function is coded in a file separate from all the other classes and functions
  • Then any functions or classes needed by main are added using the #include preprocessor directive
  • To demonstrate, we remove the main() function from our Product class as shown below
  • Note that without a main() function we can no longer compile the code
  • Trying to compile causes a linker error because every program must have a single main()function:
    ...undefined reference to `_WinMain@16'
    collect2: ld returned 1 exit status
    
  • We will show how to compile and link this code in the next section

Class Product Without a main() Function

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
#include <iostream>
using namespace std;

class Product {
public:
    Product();
    Product(string newName, double newPrice);
    string getName() const;
    double getPrice() const;
    void setPrice(double newPrice);
    void print() const;
private:
    string name;
    double price;
};

Product::Product() {
    name = "none";
    price = 0.0;
}

Product::Product(string newName, double newPrice) {
    name = newName;
    setPrice(newPrice);
}

string Product::getName() const {
    return name;
}

double Product::getPrice() const {
    return price;
}

void Product::setPrice(double newPrice) {
    if (newPrice > 0.0) {
        price = newPrice;
    } else {
        cout << "Error: negative price!\n"
             << "Setting price to 0.\n";
        price = 0.0;
    }
}

void Product::print() const {
    cout <<  name << " @ " << price << endl;
}

Check Yourself

  1. To have the preprocessor insert another file into our source code, use the ________ directive.
  2. True or false: if we want to include non-library files, use the include directive with double quote marks rather than angle brackets.
  3. True or false: in C++ you must have exactly one main() function per executable file.

13.1.2: Including a Class in an Application

  • Now let us look at how to create an application by including the separate product.cpp file
  • The following program is called productapp.cpp
  • It consists of a main() function that includes the Product class using the #include directive:
    #include "product.cpp"
  • Notice that standard library includes are placed before our custom includes

Program productapp.cpp Including a File

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 <iostream>
#include <vector>
using namespace std;

#include "product.cpp"

// For testing class Product
int main() {
    vector<Product> store;
    char repeat = 'Y';
    while ('Y' == repeat || 'y' == repeat) {
        cout << "Enter product data:\n";
        string name;
        cout << "Product name: ";
        cin >> name;
        double price;
        cout << "Price for a " << name << ": ";
        cin >> price;

        Product temp(name, price);
        store.push_back(temp);
        cout << "You entered:"
             << "\n   Name: " << temp.getName()
             << "\n   Price: " << temp.getPrice()
             << endl;

        cout << "Enter another product? (y/n): ";
        cin >> repeat;
    }

    cout << "\nAll your products:\n";
    for (unsigned i = 0; i < store.size(); i++) {
        store[i].print();
    }

    return 0;
}

13.1.2a Try It: Separating main() (5m)

  1. Copy the following program into a text editor, save it as rectangleclass.cpp, and then compile and run the starter program to make sure you copied it correctly.  You should see the following output:
Printing rec: 0 long x 0 wide
Printing rec3x5: 3 long x 5 wide
    1. #include <iostream>
      using namespace std;
      
      class Rectangle {
      public:
          Rectangle();
          Rectangle(double newLength, double newWidth);
          void print();
      private:
          double length;
          double width;
      };
      
      Rectangle::Rectangle() {
          length = 0;
          width = 0;
      }
      
      Rectangle::Rectangle(double newLength, double newWidth) {
          length = newLength;
          width = newWidth;
      }
      
      void Rectangle::print() {
          cout << length << " long x " << width << " wide\n";
      }
      
      // For testing
      int main() {
          Rectangle rec;
          Rectangle rec3x5(3.0, 5.0);
          cout << "Printing rec: ";
          rec.print();
          cout << "Printing rec3x5: ";
          rec3x5.print();
      
          return 0;
      }
      
    2. Start a new file named rectanglemain.cpp and move the main() function to this file, deleting it from the rectangleclass.cpp file.

      Try compiling rectanglemain.cpp and notice that it will NOT compile at this time. We will make it compile in the next step.

    3. Now we include the Rectangle class in the main application by adding the following code at the top of the file:
      #include "rectangleclass.cpp"
      
    4. Compile rectanglemain.cpp and run the program to verify it works the same.
    5. Be prepared to answer the following Check Yourself questions when called upon.

    Check Yourself

    1. True or false: we can create an application using two separate source code files.
    2. We can include the following number of files in our code: ________.
      1. 1
      2. 2
      3. 4
      4. as many as we need
    3. True or false: standard library includes should be placed before custom includes.
    4. To include a custom library named "mylib.cpp" we add the code: ________.
      1. #include (mylib.cpp)
      2. #include [mylib.cpp]
      3. #include "mylib.cpp"
      4. #include <mylib.cpp>


    13.1.3: Separate Compilation Process

    • We can take the separation of files even further
    • C++ lets us divide a program into separate parts and compile each part separately
    • After all the parts are compiled, the parts are linked together into an executable program
    • We can see the compilation steps in the following diagram

    Compilation Process

    Benefits of Separating the Parts

    • We can place the definition for a class and its functions in files separate from the programs using the class
    • This allows us to build libraries of classes
    • These libraries can be used by many different programs
    • In addition, we can compile a class just one time and use it in many different programs
    • We use a similar process with standard libraries like iostream
    • Moreover, we can define the class itself in two files with the definition of the class in one file and the implementation in another
    • If we only change the implementation of the class, then we only need to recompile the implementation
    • Other files in the program, including files that use the class, need not be changed or recompiled

    Encapsulation Reviewed

    • The principles of encapsulation and data hiding tells us to separate the interface from the implementation
    • The separation should be complete enough that we can change the implementation without changing the program that uses the class
    • We ensure this separation with the following three rules

    Rules of Separation

    1. Make all member variables private
    2. Make each basic operation of the class a public member function. All the public functions become the interface
    3. Make the member function implementations unavailable to the programmer who uses the class

    More Information


    13.1.4: Example of Separate Compilation

    • Separate compilation starts by placing class interfaces and implementations in separate files
    • The interface is placed in a header file with a .h extension
    • The implementation code is placed in an implementation file with a .cpp extension
    • For example, we can save our Product class interface in a header file
    • We name the file: product.h
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    #ifndef PRODUCT_H
    #define PRODUCT_H
    
    #include <string>
    using namespace std;
    
    class Product {
    public:
        // Constructors
        Product();
        Product(string newName, double newPrice);
        // Member functions
        string getName() const { return name; }
        double getPrice() const { return price; }
        void setName(string newName);
        void setPrice(double newPrice);
    private:
        string name;
        double price;
    };
    
    #endif
    

    #include Guards

    • Notice the use of:
      #ifndef PRODUCT_H
      #define PRODUCT_H
      ... (class declaration)
      #endif
      
    • These are know as #include guards
    • We use these constructs to avoid the problem of double inclusion
    • Once a header files is included, the preprocessor checks if a unique value is defined
    • If the value is not defined, then it defines it and continues with the file
    • If the value is already defined elsewhere, the first ifndef fails and results in a blank file being included
    • We typically use a naming scheme of the class name followed by "_H" to define the header files

    Implementation (Function Definition) Files

    • The remainder of the class definition stays in the .cpp file, such as product.cpp
    • However, the .cpp function definitions still need the declarations to compile
    • Thus, we add them back by including the product.h file in the .cpp
    • The following is an example of product.cpp where the function headers (declarations) have been placed in a separate .h file
    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
    
    #include "product.h"
    
    // no-parameter constructor
    Product::Product() {
        name = "Unknown";
        price = 0.0;
    }
    
    Product::Product(string newName, double newPrice) {
        setName(newName);
        setPrice(newPrice);
    }
    
    void Product::setName(string newName) {
        if (newName.length() == 0) {
            name = "Unknown";
        } else {
            name = newName;
        }
    }
    
    void Product::setPrice(double newPrice) {
        if (newPrice > 0.0) {
            price = newPrice;
        } else {
            price = 0.0;
        }
    }
    
    

    Application File

    • The main() function is the starting point of any application
    • We usually place main() in a separate file and include the header files we need for compiling
    • Notice that the includes for our header files are placed after the standard library includes
    • For our example, we call our main file productapp.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
    
    #include <iostream>
    using namespace std;
    
    #include "product.h"
    
    // For testing class Product
    int main() {
        char choice = 'Y';
        string name;
        double price;
        while ('Y' == choice || 'y' == choice) {
            cout << "Enter a product name: ";
            cin >> name;
            cout << "Enter the price for a "
                 << name << ": ";
            cin >> price;
    
            Product prod(name, price);
            cout << "You entered:"
                 << "\n   Name: " << prod.getName()
                 << "\n   Price: " << prod.getPrice()
                 << endl;
    
            cout << "Enter another product? ";
            cin >> choice;
        }
    
        return 0;
    }
    

    Compiling

    • Because we have multiple .cpp files, compiling is now a two-step process:
      1. Compile the all the .cpp files into object files
      2. Link all the objects files together into an executable file
    • We compile the class and the application into object files:
      g++ -c product.cpp
      g++ -c productapp.cpp
      
    • Then we link all object files together into an application
      g++ -o productapp productapp.o product.o
      
    • We then run the application in the usual way:
      ./productapp
      


    13.1.5: Instructions for Separate Compilation

    1. Separate the interface from the implementation
      1. Place the class declaration into a classname.h file
      2. Place #include guards around the declaration in the classname.h file
        #ifndef PRODUCT_H
        #define PRODUCT_H
        // code goes here
        #endif
        
      3. Place the class implementation (function definitions) into a file named classname.cpp
      4. Code a #include "classname.h" directive in the classname.cpp file and place it after the standard library includes
    2. Place the main() function in a separate file
      1. Place the application main function into a file like appname.cpp
      2. Code a #include "classname.h" directive in the appname.cpp file
        #include "product.h"
    3. Compile the class and the driver into object files:
      g++ -c classname.cpp
      g++ -c appname.cpp
      
      for example:
      g++ -c product.cpp
      g++ -c productapp.cpp
      
    4. Link both files together into the application
      g++ -o appname appname.o classname.o
      
      like:
      g++ -o productapp productapp.o product.o
      
    5. Then run the application in the usual way:
      ./appname
      for instance:
      ./productapp
      

    13.1.5a Try It: Separate Compilation (10m)

    1. If you are using a Windows computer, open the Cygwin terminal window.
    2. Copy the following program into a text editor, save it as myrectangle.cpp, and then compile and run the starter program to make sure you copied it correctly.  You should see this output:
    length: 3
    width: 5
      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
      
      #include <iostream>
      using namespace std;
      
      class MyRectangle {
      public:
          MyRectangle(double length, double width);
          void print() const;
      
      private:
          double length;
          double width;
      
      };
      
      MyRectangle::MyRectangle(double newLength, double newWidth) {
          length = newLength;
          width = newWidth;
      }
      
      void MyRectangle::print() const {
          cout << "length: " << length
               << "\nwidth: " << width
               << endl;
      }
      
      // For testing
      int main() {
          MyRectangle rec(3.0, 5.0);
          rec.print();
      
          return 0;
      }
      
    1. Apply the separate compilation process to the MyRectangle class shown above. You should end up with three files:
      • myrectangle.h
      • myrectangle.cpp
      • myrectangleapp.cpp
    2. Make sure you have #include guards in the myrectangle.h file.
      #ifndef MYRECTANGLE_H
      #define MYRECTANGLE_H
      // code goes here
      #endif
      
    3. Make sure you place the class declaration into the myrectangle.h and then add a #include "myrectangle.h" directive at the top of the myrectangle.cpp file.
      #include "myrectangle.h"
      
    4. Make sure you place the main() function in a separate file and add a #include "myrectangle.h" directive at the top of the file but after using namespace std;
      #include "myrectangle.h"
      
    5. At the command line, compile the files separately, like:
      g++ -c myrectangle.cpp
      g++ -c myrectangleapp.cpp
      
    6. Link both files together into an application like:
      g++ -o myrectangleapp myrectangleapp.o myrectangle.o
      
    7. Then run the application in the usual way to test the file:
      ./myrectangleapp
      
    8. You should see the same output as before:
    length: 3
    width: 5

         11.  Check to see if the student on either side of you needs help. If so, offer to help them.


    13.1.6: Makefiles

    • It quickly becomes tedious to recompile code with multiple source files
    • We can use a program named make to automatically recompile our files
    • However, we must create a file named Makefile that has the instructions for the makeprogram
    • Note that the large blank areas before a command is a tab character
    • To invoke a Makefile, we type make at the command line
    • Most people write a Makefile by modifying an existing one

    Makefile Example

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    # simple makefile
    
    # define target dependencies and files
    productapp: productapp.o product.o
    	g++ -o productapp productapp.o product.o
    
    # define how each object file is to be built
    productapp.o: productapp.cpp product.h
    	g++ -c productapp.cpp -Wall -Wextra -Wpedantic -std=c++11  
    
    product.o: product.cpp product.h
    	g++ -c product.cpp -Wall -Wextra -Wpedantic -std=c++11 
    
    # clean up
    clean:
    	rm -f productapp.exe *.o
    
    

    Important Information

    • Lines starting with a hash mark (#) are comments and are ignored
    • Rules define which files depend on others and take the form:
      targetfile : sourcefiles
      <tab>commands you normally type
      
    • tab character is required prior to defining the commands
    • If using Notepad++, we need to configure the preferences for the Text document class to NOT substitute spaces for tabs.
    • Save without a .txt extension by saving as type "All Files (*.*)"

    Further Information


    Exercise 13.1: Separate Compilation and Makefiles (5m)

    In this exercise we create a Makefile to make separate compilation easier.

    Specifications

    1. If you have not already applied the separate compilation process to the MyRectangle class, complete the exercise: Try It: Separate Compilation. Make sure you have three separate files:
      • myrectangle.h
      • myrectangle.cpp
      • myrectangleapp.cp
    2. If you don't have these files click HERE
    3. Copy the Makefile Example below and save it as Makefile without a .txt extension by saving as type "All Types (*.*)" 
    4. We must save the Makefile with tab characters. Thus on Windows Notepad++ go to: Settings->Preferences->
         Languages->uncheck the box on the right side "replace by space"   (You will have to change this back again when you work on other code files).
    5. in a cygwin window:  type  "make"    (without the quotation marks)
    6. next,  type    "./myrectangleapp"        (without the quotation marks)
    7. Your output should be:
    8. length: 3
      width: 5
    9. Zip and submit your three code files and one Makefile as the solution to this exercise.  call your zip file:  myrectangle.zip
      • Makefile
      • myrectangle.h
      • myrectangle.cpp
      • myrectangleapp.cpp

      Note: please zip (archive) files to be able to submit the Makefile.  Do this by selecting the 4 files,  right click and Send To->Compressed(zipped) folder

    Makefile Example

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    # simple makefile
    
    # define target dependencies and files
    productapp: productapp.o product.o
    	g++ -o productapp productapp.o product.o
    
    # define how each object file is to be built
    productapp.o: productapp.cpp product.h
    	g++ -c productapp.cpp -Wall -Wextra -Wpedantic -std=c++11
    
    product.o: product.cpp product.h
    	g++ -c product.cpp -Wall -Wextra -Wpedantic -std=c++11
    
    # clean up
    clean:
    	rm -f productapp.exe *.o
    
    


    13.1.7: Summary

    • C++ allows us to place the interface and the implementation in separate files
    • We can write a Makefile for easy recompiling

    Check Yourself

    1. What are the steps for separate compilation?


    13.1.8 switch Statements

    • The switch statement provides an alternative to an if-else-if chain
    • Executes a section of code depending on the value of a single number
    • The general syntax is:
      switch (integerExpression) {
         case label1:
            statements
            break;
         case label2:
            statements
            break;
         ...
         case labeln:
            statements
            break;
         default:
            statements
      }
      
    • Where:
      • integerExpression: an arithmetic expression that resolves to an integer number
      • labelx: an integer constant
      • statements: the statements to execute when the condition is met
    • Any number of case labels can be placed in any order
    • When run, the integerExpression tries to match one of the case labels
    • If a label matches then the statements after the case label start executing
    • Any value that does not match starts executing with the statement after default
    • Execution continues until the end of the switch statement or encountering a break statement
    • The break statement causes an immediate exit from the switch statement
    • Just as case identifies possible starting points, break determines end points

    Example Program Using a switch Statement

    int main() {
    int num;
        cout << "Enter a 1, 2 or 3: " << endl;
    cin >> num;
    switch (num) {
    case 1:
    cout << "Apple" << endl;
    break;

    case 2:
    cout << "Banana" << endl;
    break;

    case 3:
    cout << "Orange" << endl;
    break;

    default:
    cout << "Invalid Entry" << endl;
    }
    }

    When to Use switch Statements

    • Switch statements can only be used when
      • The are integers or chars 
      • You are looking for exact matches
    • Thus, switch statements are inherently less useful than if-else statements
    • Also, the syntax is no clearer than if-else statements
    • There is a reason for the limitations of the switch statement
      • The compiler can generate faster code for switch statements
      • Though you shouldn't count on it...
    • However, modern compilers are quite capable of optimizing if-else statements 
    • Be careful when using a switch statement
      • Every branch of the switch statement must be terminated by a break statement
      • If the break statement is missing, the program falls through and executes the next case without testing
      • There are rare uses for this fall through behavior, such as printing the words for the song, The Twelve Days of Christmas
      • However, according to a study by Peter van der Linden, reported in his book, Expert C Programming, p. 38, the falling through behavior is needed less than 3% of the time
      • Thus, the default behavior is wrong 97% of the time
    • Forgetting to type the break statement is a very common error and the source of many bugs
    • So one has to ask oneself, "Why use an inferior programming statement that causes more problems than it solves?"

    Check Yourself

    1. True or false: C++ switch statements allow testing for inequalities like < and >.
    2. True or false: the default behavior of a switch statement, to continue executing past the next label, is usually correct.
    3. True or false: programmers never forget to include a break statement when needed inside a switchstatement.


    13.2: 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.2.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

    ModuleDescription
    Systemgeneral utilities such as vector classes, conversion functions and timing classes
    Windowprovides windows, events and input handling
    Graphics2D graphics library for drawing shapes, text and sprites
    Audioclasses for playing sounds, streaming music, recording and spatialization
    Networkutilities 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

    More Information


    13.2.2: Starting a Graphical Project

    • Here is an SFML project to get us started on Windows: start-sfml
      • save the zip file to c:\CodeBlocks\sfml\projects
      • 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
    • 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.2.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

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

    1. Here is an SFML project to get us started on Windows: start-sfml
      • save the zip file to c:\CodeBlocks\sfml\projects
      • 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
    2. Unzip the archive file
    3. Once unzipped, we extract the file and double-click the file: start-sfml.cbp
    4. Verify that the main.cpp window contains the same code as SFML Window with Event Handling program from above.
    5. Build the project by clicking on the Build icon cb build icon  in the toolbar, using the menus Build->Build, or pressing Ctrl+F9.
      cb build
    6. 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.

    1. Run the program by clicking on the Run icon cb run icon in the toolbar, using the menus Build->Run, or pressing Ctrl+F10, and verify you see the following graphics window.  Press the escape key to close the graphics window.

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

      hello sfml
    2. After closing the graphics window by pressing the red X, verify the message process returned 0 appears in the console.
      sfml end
    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.2.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

    FunctionDescription
    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;
    }
    

    13.2.4aTry It: Explore Mouse Clicks (3m)

    1. Open your CodeBlocks project from the last Try It.  start-sfml
    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.2.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

    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()
    Sample Code to draw a Green Circle

    #include <SFML/Graphics.hpp>

    int main()

    {

        sf::RenderWindow window(sf::VideoMode(200, 200), "SFML works!");

        sf::CircleShape shape(100.f);

        shape.setFillColor(sf::Color::Green);

        while (window.isOpen())

        {

            sf::Event event;

            while (window.pollEvent(event))

            {

                if (event.type == sf::Event::Closed)

                    window.close();

            }

            window.clear();

            window.draw(shape);

            window.display();

        }

        return 0;

    }

    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.


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

       

    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.2.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

    FunctionDescription
    move(dx, dy)Moves the object by (dxdy) 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
      screen 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));
    
        // 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

    13.2.6a Try It: Drawing and Transforming Shapes (4m)

    1. Open your CodeBlocks project from the last Try It   start-sfml
    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));
      

       

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

       


    13.2.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 (RedGreenBlue)
    • 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 redgreen 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 ConstantRed, Green, Blue
    sf::Color::Red255, 0, 0
    sf::Color::Blue0, 0, 255
    sf::Color::Yellow255, 255, 0
    sf::Color::Black0, 0, 0
    Color ConstantRed, Green, Blue
    sf::Color::Green0, 255, 0
    sf::Color::Magenta255, 0, 255
    sf::Color::Cyan0, 255, 255
    sf::Color::White255, 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

    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:
      a)  sf::Color red(     ,   ,   ); 
      b)  sf::Color green(   ,   ,   ); 
      c)  sf::Color blue(   ,   ,    ); 
      d)  sf::Color cyan(   ,   ,   ); 
      
    4. What are two ways to complete the following statement? 
      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. Enter the code for declaring a translucent blue color: 
       
    Answers:  3a)  255, 0 0  3b)  0, 255,  0   3c)  0, 0, 255  3d)  0,255, 255
    4.  sf::Color::Magenta or sf::Color(255, 0, 255);
    5.  orange(255, 165,  0)  purple(128, 0, 128)
    8.  sf::Color translucentBlue(0, 0, 255, 128);

    More Information


    Exercise 13.2: 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

    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  start-sfml

      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);
      

      CircleShape object requires a radius. For more information, see section: 13.2.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. You can choose your own colors from the Constants or color chart.

    7. 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.2.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. You can choose whatever eye color you would like.

    8. Create and draw a nose using similar code as for drawing the eyes. Some suggested settings:
      const int NOSE_SIZE = 30;
      
    9. 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.2.5: Creating Graphical Shapes.

    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.2.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.2.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.2.4: Keyboard and Mouse Inputs

    13. Submit your main.cpp code to Canvas.


    13.3: 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

    ^ top

    13.3.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.

    ^ top

    13.3.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

    ^ top

    13.3.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;
    }
    

    ^ top

    13.3.4: 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;
    }
    

    ^ top

    13.3.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;
    }
    

    ^ top

    Exercise 13.3: 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.2.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. Turn this main.cpp into Canvas.