Skip to content

Latest commit

 

History

History
316 lines (259 loc) · 18.1 KB

ch_arraylist.asciidoc

File metadata and controls

316 lines (259 loc) · 18.1 KB

Create an ArrayList of Objects

This sketch illustrates how to create and manage a list of interactive objects. Whether you’re writing games, particle systems, or an Arduino controlled magic show, these are tools you’ll use again and again as you go further with Processing.

Next, we need to do is create a nice font for the target. (Fonts are discussed in Chapter 6 of Getting Started with Processing). Why, you ask? Out of the box, Processing has just a simple command called text() that will display text, but you can’t tell how much space they occupy on the screen (which will be important, as we’ll see in a minute). Plus, they’re just you’re plain old Arial font, so they aren’t very interesting.

To get around this, you can use the Tools menu to create a new font file for your sketch. After you import the file using the loadFont() command, you’ll be able to play around with typography. (W00t!). Anyway, the process for creating a font is straightforward. First, click "Tools → Create Font…​" from the menu bar, which will pop up the following screen similar to Creating a font in Processing:

arraylist create font
Figure 1. Creating a font in Processing

Then, all you have to do is select the font you want (I chose "Baskerville-Bold"; if you want to use a different font, you’ll have to change the name of the font file in magic_words.pde). Notice that the file name at the bottom of the box changes as you select different options — this is the file name you’ll need to use in the sketch; Processing will add ".vlw" to the end automatically.

Once you have the two files loaded and the font installed, run the sketch. (As with many of the projects, you’ll also need a webcam.) Using the the wand (or any other suitable pointer) from the last Codebox, you should be able to swat the various targets.

The next two sections describe how the sketch works. The first part describes Target class, the main building block of the script. The second section describes how the magic_words.pde sketch uses and ArrayList to manage multiple Target objects.

The Target object

Chapter 9 of Getting Started with Processing introduces objects and object-oriented programming (OOP). To review briefly, an object is a building block used to create more sophisticated programs. Objects contain two basic elements: fields, which are variables that determine the object’s current state, and methods, which are just functions that make the object do something. A key part of the power of OOP is that it helps you think about your code in a physical way by encouraging you to think in terms of simpler components.

To use objects, you first create a class (or several classes — you can use many different objects), which is like a template that describes everything the object can do. Once you’ve defined the class, you use the "new" command to actually create objects that you can use in your sketches. This distinction can be a bit confusing, so think of it this way. If you were baking a cake, you’d start with a recipe. This is like a class — you have a description, but a cake itself. To get the cake — or object — you have to follow the recipe before you have anything to eat.

Creating new objects is the job of Processing’s new command — it makes a new object variable by calling a special method called the constructor. The constructor initializes the object’s variables and generally gets it ready for action; the only difference between it and any other method is that it has the same name as the class itself.

Note

You can find the code listing for this project, magic_words.pde, on this book’s github repository.

OK, enough review. Let’s talk about the code. Let’s first look at the Target class, which appears at the end of the sketch:

class Target {

  float x, y, dx, dy; // (1) (2)
  float w, h;  //Width and height of the box // (3)
  int fontSize = 48; // (4)
  Boolean inTarget = false; // (5)
  String currentText, beforeText, afterText; // (6)

  // Constructor -- called when the object is created with "new"
  Target(String _beforeText, String _afterText) { (7)
     beforeText = _beforeText;
     afterText = _afterText;
     currentText = _beforeText;
     x = random(0,width);
     y = random(0,height);
     dx = random(-5,5);
     dy = random(-5,5);
     setBox();
  }

  // Advances the object to a new position
  void step() { // (8)
    x += dx;
    y += dy;
  }

  // Determines if the object is still on the stage
  Boolean onStage() { // (9)
    Boolean retVal = false;
    if (((x+w) > 0) && (x < width) && (y > 0) && ((y-h) < height)) {
        retVal = true;
    }
    return retVal;
  }

  // Set the height and width of the bounding text box
  void setBox() { // (10)
     h = fontSize;
     textFont(font, fontSize);
     w = textWidth(currentText);
  }

  // Determines is a particular x,y coordinate is within the box
  boolean detectCollision(float cx, float cy) { // (11)
     boolean retVal = false;
     if ( (cx > x) && (cx < (x+w)) && (cy > (y-h)) && (cy < y)) {
        retVal= true;
     }
     return retVal;
  }

  // Toggles the which target word is displayed
  void toggle() { // (12)
     if (currentText == beforeText) {
        currentText = afterText;
     } else {
       currentText = beforeText;
     }
     setBox();
  }

  // Displays the text at x,y
  void paint() { // (13)
    fill(255);
    textFont(font,fontSize);
    textSize(fontSize);
    text(currentText,x,y);
  }

}

As discussed earlier, the Target class is a building block. Its fields include:

  1. Current position, as represented by the variables "x" and "y"

  2. Speed and direction, as represented by the variables "dx" and "dy". These are just randomly chosen values.

  3. Width and height, as represented by the variables "w" and "h"

  4. Font size (more on this in a bit)

  5. A flag indicating whether the wand is touching the target (flag is the programming term for any variable used to represent a specific status, like on or off)

  6. The current text of the target (i.e., "Hat" or "Rabbit")

  7. After the mandatory constructor Target(), whose job is to set the values of the before and after text for the target, the methods include the following.

  8. step() makes the target move around the stage. This works by adding the the "dx" and "dy" to the "x" and "y" variables.

  9. onStage() returns a flag indicating if the target has flown off the visible screen area. Once it’s flown out, it’s recycled by putting it at some new spot with a new speed and direction. It’s comforting to note that despite the added complexities, most of this code is almost identical to Example 5-17: The Bounds of a Rectangle, in Getting Started with Processing.

  10. setBox() updates the target’s width and height variables based on whether it’s been touched by the wand

  11. detectCollision() determines if the wand has collided with the target. If you’ll recall from the initial Codebox, collision detection is name for all the ways you can determine it two things are intersecting on the screen. In the original wand example, we had a circular target, so we used a simple distance formula to determine if the wand’s coordinates were inside the radius of the target. In this example, we’re using a rectangular target. To make things a bit more complicated, the target changes size when the want touches it, so we need some supporting methods to make sure all the variables are in line with the current state of the target. Determining the size of the target shows the various pieces that are in play.

  12. toggle() changes the target word. (i.e., if the current word is "Hat," then toggle() makes it "Rabbit," and vice versa)

  13. paint() draws the target on the stage at the ("x", <i>y</i>) position.

arraylist target size
Figure 2. Determining the size of the target

Arraylist of targets

Now that we’ve gone through the Target class, let’s take a look at how it’s used. Here is the rest of the sketch:

import processing.video.*;

Capture cam;

PFont font;


// Variables to determine the size of the box used to acquire a target
int colorDist = 50; // Controls how close the current pixel color
                    // must match to the target color

color targetColor =  color(255,255,255);   // Color of the target
boolean acquireMode = true;
int targetX = 10;
int targetY = 25;
int targetSide = 10;

// Used to find the geometric center of the target based on an average
float wandX = 0;
float wandY = 0;
boolean wandFound = false;

ArrayList targets;  // an ArrayList is a dynamic way to manage arrays (1)
int N = 5;  // Number of targets to keep on the screen at any given time

void setup() {
  size (640, 480); // set to 4x3 aspect ratio for screencasting
  cam = new Capture(this, width, height);
  frameRate(60);
  //Be sure to create this font using "Tools -> Create Font"
  font = loadFont("Baskerville-Bold-48.vlw");
  targets = new ArrayList();  // Create a new list (2)
  for (int i=0; i < N; i++) {
    targets.add(new Target("Hat", "Rabbit")); // (3)
  }

}

void draw() {
  if (cam.available()) {
    cam.read();
    image(cam,0,0);
  }

  if(acquireMode) {
    // Display the current target color
    strokeWeight(1);
    fill(targetColor);
    rect(targetX,targetY - 2 * targetSide,targetSide, targetSide);
    // Display the acquisition box
    fill(color(255,255,255));
    rect(targetX,targetY,targetSide, targetSide);
    textSize(10);
    text("Place target in square and press any key when done.",
        targetX + 1.5 * targetSide,targetY + targetSide);
    // Set a new random color gradient
    targetColor = acquireTargetColor();
  }
  else {
    searchForTargetColor();
    // Process the list of targets
    fill(255,0,0);
    ellipse(wandX,wandY,10,10);
    for  (int i=0; i < targets.size(); i++) {
       // Fetch the i'th target from the Array
       Target t = (Target) targets.get(i); // (4)
       t.paint();  // Paint it (5)
       // Check for collisions
       if (t.detectCollision(wandX, wandY)) { // (6)
          if (! t.inTarget) {
             t.toggle();
             t.inTarget = true;
          }
       } else {
         t.inTarget = false; // (7)
       }
       t.step(); // Advance it on the screen (8)
       // If the current target has moved off the stage, delete it
       // from the list and create a new target
       if (!t.onStage()) { // (9)
          targets.remove(i);
          targets.add(new Target("Hat", "Rabbit"));
       }
    }
  }
}

// Finds the average target color that has been placed in the target box
// Loops through each pixel in the target acquisition area and determines
// the "average" color
color acquireTargetColor() {
  int r = 0;
  int g = 0;
  int b = 0;
  int cnt = 0;
  cam.loadPixels();
  for (int i = 0; i < targetSide; i++) {
    for (int j=0; j < targetSide; j++) {
      cnt += 1;
      int x = targetX + i;  //x point inside the target box
      int y = targetY + j;  //y point inside the target box
      // Pull out the current pixel color
      color c = cam.pixels[y*width + x];
      r += red(c);
      g += green(c);
      b += blue(c);
    }
  }
  targetColor = color(r/cnt, g/cnt, b/cnt);
  return targetColor;
}

// Searches for the target color.  Searches each pixel in the entire image
// and compares it to the target color.  If the distance is less than the
// threshold colorDist, it's assummed to be a match
void searchForTargetColor() {
  // Reset wand
  wandX = 0;
  wandY = 0;
  wandFound = false;
  cam.updatePixels();
  //Now search for pixels that match the target color
  int numPoints = 0;  //Number of points found
  int sx = 0;  //Sum of all x coordinates found
  int sy = 0;  //Sum of all the y coordinates found
  for (int i=0; i < width; i++) {
    for (int j=0; j < height; j++) {
      color pix = cam.pixels[j*width + i]; //Grab pixel at i,j
      float dr = red(targetColor) - red(pix);
      float dg = green(targetColor) - green(pix);
      float db = blue(targetColor) - blue(pix);
      float d = sqrt ( pow(dr,2) + pow(dg,2) + pow(db,2));
      // If it's a match, then keep a running total
      if (d < colorDist) {
        numPoints += 1;
        sx += i;
        sy += j;
      }
    }
  }
  // If we found the target color, set the wand coordinates
  if (numPoints > 0) {
    wandX = sx / numPoints;
    wandY = sy / numPoints;
    wandFound = true;
  }
}


// Toggle the acquire mode
void keyPressed() {
  acquireMode = !acquireMode;
}

As mentioned at the outset, the main goal of this example was to show how you could manage multiple objects dynamically. While standard arrays (discussed in Chapter 10 of Getting Started with Processing) are great for many things, they’re not very dynamic because once you’ve specified how many elements they have, you’re stuck with that forever.

For example, suppose you wanted to have a bunch of targets, but rather than recycling them like we’ve done here, you simply wanted to delete them. In a standard array, you can’t delete elements. If you started with 5, you always have 5, no matter what. So, if you want to "delete" something, you have to have some sort of clumsy workaround. Or, on the flip side, suppose you had a program where you usually had a handful of items to manage, but on some occasions, you might have many thousands. Using a standard array, you’d have to create space for thousands every time, which wastes memory and can make your sketches slower.

Processing’s ArrayList is a way around these limitations. Rather than being a simple data type, like float or int, an ArrayList is a building block class for managing other objects. It has methods for adding new items, deleting existing one, finding how many items are in the list, and so forth. In addition to being a really useful tool, working with ArrayList will familiarize you with the techniques you’ll use in other, more sophisticated classes, like HashMaps (something we’ll explore in future posts). So, let’s look at the code.

  1. Declaring an ArrayList is fairly simple, and is done in this line.

  2. Once it’s been declared, we can create it. Note how we don’t have to declare a size.

  3. Here, we start adding some new objects into the ArrayList using the add() method. This code has a few interesting points. First, it demonstrates the syntax for calling a method, which is object "variable.method (argument list)". Second, it shows how you can use an object as an argument to ArrayList. Note how we use the new command in the argument list — that will create a new Target object variable and pass it into the list. Finally, the snippet shows how we can do all this inside a loop. We could add 10, 100, or 10,000 objects — the size of the list is completely dynamic.

  4. Here, we use the get() method to pull an item from the ArrayList. Several things are happening. The first is that we’re creating a new Target variable called "t". This time, though, we’re not using the new command. Why, you ask? It’s because the object we’re trying to access already exists — it was created earlier in the setup() method. All we’re doing here is retrieving it. Second, we have this kind of weird syntax of (Target). This is the way that we tell Processing what type of variable it is that we’re expecting to retrieve. This is called "casting," and requires a bit more explanation.

    If you’ll recall, ArrayList is a generic tool — we could use it with a Target variable, the Jitterbug class discussed in Chapter 10 of Getting Started with Processing, or any other class variable we might create in the future. However, Processing (and Java, the underlying language it’s based on) can’t deal with generic object. It requires that we give every variable an exact type when we declare it. Adding (Target) to the front of the get() command is what does this — we’re telling Processing "Hey, we’re pulling off a 'Target' object." You’ll see this referred to as casting in other programming resources.

    Finally, we have the "targets.get(i)" part of the line. This is just saying "pull in the element at position i from the targets ArrayList."

  5. Once we’ve actually retrieved the variable t, we can read its variables, call its methods, and generally make it do our bidding. In this example, we first call the paint() method to draw the target on the screen.

  6. Next, we check to see if the wand is touching it (as you’ll recall from the the magic wand Codebox, the wand’s position is represented by the variables wandX and wandY). If there is a collision, we first check to see if the wand was already inside the target. (In other words, the wand might have collided with the object on an earlier iteration of setup() and still be inside the target box.) If it’s not (i.e., it’s hitting the target box for the first time), then we toggle the text and set the inTarget flag.

  7. If the wand is already in the target, then we simply set the target flag to false, which prevents the target from alternating state on each iteration of draw().

  8. This command simply increments the target’s x and y positions.

  9. Finally, the last if block checks to see if the target is no longer visible on the stage. If it’s not (i.e., it has wandered off the visible area of the screen), then the target is removed from the ArrayList using the remove() method; a new target is then added in at some random new place.

Whew! That’s a lot of abstract stuff, but it’s worth understanding because you will use these techniques again and again. Whether you’re using an ArrayList, a HashMap, or some other general data structure, understanding these basic steps — creating a new object, storing it in a generic data structure, and retrieving it — is essential in making more sophisticated programs.