Sprites

In these notes you will learn:

  • How to write a simple class.
  • How to use objects with ”.”-notation.
  • How assignment works with object variables.
  • What null is.

Introduction

Every 2-dimensional animated object needs, at least, these four variables:

float x;  // (x, y) is the position
float y;  // of the object
float dx; // speed along the x-axis
float dy; // speed along the y-axis

So, for example, if you want to animated 3 different objects, you will need at least 12 variables:

float x1;  // object 1
float y1;
float dx1;
float dy1;

float x2; // object 2
float y2;
float dx2;
float dy2;

float x3; // object 3
float y3;
float dx3;
float dy3;

Dealing with so many similarly-named variables is quite difficult in practice. It’s easy to accidentally write y2 instead of y1.

Sprites

One way to deal with this complexity is to group x, y, dx, and dy together into one object. To do this in Processing, we first need to create a class:

class Sprite {
    float x;
    float y;
    float dx;
    float dy;
}

Using the Sprite class, we can now create Sprite objects like this:

Sprite ball = new Sprite();
Sprite plate = new Sprite();

When new Sprite() is called, it creates a new object that contains its own x, y, dx, and dy values. We can use it like this:

ball.x = 10;   // initialize ball
ball.y = 10;
ball.dx = 1;
ball.dy = 1.5;

plate.x = 450; // initialize plate
plate.y = 450;
plate.dx = -1.5;
plate.dy = -1;

The . is used to access the variables in an object. Clearly, ball.x refers to the x in the ball object, while plate.x refers to the x in plate.

Two advantages of using the Sprite class are:

  • The variable names tend to be more descriptive.
  • You don’t have to define as many variables.

While this doesn’t solve all the problems of so many variables, it is an important first step. Later, we will see how other features can be used to even further simplify the creation of animated objects.

Note

Sprite is the traditional name for a 2-dimensional animated object. It originated in early video games, where support for animated objects was often designed into the hardware itself.

Sample Program

Here’s a complete sample program using a Sprite to represent a ball:

class Sprite {
  float x;
  float y;
  float dx;
  float dy;
}

color white = color(255);
color orange = color(255, 165, 0);

Sprite ball = new Sprite();
float diam;

void setup() {
  size(500, 500);
  smooth();
  ball.x = 100;
  ball.y = 100;
  ball.dx = 1;
  ball.dy = 2;
  diam = 50;
}

void draw() {
  background(white);

  noStroke();
  fill(orange);
  ellipse(ball.x, ball.y, diam, diam);

  ball.x += ball.dx;
  ball.y += ball.dy;

  // hit the left edge?
  if (ball.x - diam / 2 <= 0) {
    ball.dx = -ball.dx;
    ball.x = diam / 2;
  }

  // hit the right edge?
  if (ball.x + diam / 2 >= 499) {
    ball.dx = -ball.dx;
    ball.x = 499 - diam / 2;
  }

  // hit the top edge?
  if (ball.y - diam / 2 <= 0) {
    ball.dy = -ball.dy;
    diam += 10;
    ball.y = diam / 2;
  }

  // hit the bottom edge?
  if (ball.y + diam / 2 >= 499) {
    ball.dy = -ball.dy;
    ball.y = 499 - diam / 2;
  }
}

Assigning Object Variables

Consider this code fragment:

Sprite ball1 = new Sprite();
Sprite ball2 = new Sprite();

ball1.x = 100;
ball2.x = 200;

println(ball1.x);  // prints 100
println(ball2.x);  // prints 200

We say ball1 and ball2 refer, or point, to their corresponding Sprite objects. ball1.x and ball2.x thus refer to different variables.

Now suppose we write this:

Sprite ball1 = new Sprite();
Sprite ball2 = new Sprite();

ball1 = ball2;

ball1.x = 100;
ball2.x = 200;

println(ball1.x);  // prints 200
println(ball2.x);  // prints 200

The result is different because the assignment statement ball1 = ball2; makes ball1 refer to the same object as ball2. Thus ball1.x and ball2.x refer to the same variable.

What’s important to notice here is that ball1 = ball2; does not make a copy of the underlying object. Instead, it makes the variable ball1 point to the same variable ball2 points to.

Garbage Collection

There is a subtle but important issue hidden in the previous example:

Sprite ball1 = new Sprite();
Sprite ball2 = new Sprite();

ball1 = ball2;

ball1.x = 100;
ball2.x = 200;

After the assignment statement ball1 = ball2; is executed, what happens to the object that ball1 use to refer to? ball1 no longer refers to it, and so there is now no way to access the variables that are in it. An object that has no references or pointers to it is called a garbage object. Garbage objects cannot be used by your program, but still take up memory. If you have too many garbage objects, your program’s performance could degrade, or your program may even crash.

In some languages, such as C or C++, garbage objects must be manually deleted by the programmer. Deleting a garbage object means telling the program that the memory it uses can, if needed, be given out to other new objects. Experience has shown that manually deleting garbage objects is surprisingly difficult and error-prone, even for the best programmers. Great care must be taken to ensure that an object truly is garbage, i.e. that nothing points to it.

Java does not have manual garbage deletion, and instead handles garbage objects in two main ways:

  1. Sometimes it does nothing! In small programs that don’t create a lot of objects, there may be no need to delete garbage objects. Just let the garbage objects accumulate, and ignore them. As long as you enough memory, this may not be a big a problem.

    The good thing about this approach is that it is fast, simple, and easy to understand. But the bad thing is that it ignores the problem of garbage instead of solving it. In longer running programs, small garbage objects can add up to waste a lot of memory, requiring they they be deleted somehow.

  2. Run an automatic garbage collector sub-program. When you create a Java program, Java automatically gives it a special garbage collector sub- program that will, when it decides it is necessary, automatically delete garbage objects in your main program. The garbage collector sub-program does take a bit of time when it runs, and so because of this Java programs are not necessarily as efficient as equivalent programs in languages with manual garbage deletion.

    The nice thing about automatic garbage collection is that the programmer doesn’t need to even know about it: it is totally automatic. The actual details of how garbage collectors are quite subtle and involved, but using it is easy.

null and NullPointerException

If you create a Sprite variable without making it point to a Sprite object, then it is assigned the special value null:

Sprite ball;    // ball is automatically set to null

println(ball);  // prints "null"

null means that the variable is not referring to any object. You can never use ”.”-notation with null, e.g.:

Sprite ball = null;

ball.x = 5;   // run-time error: NullPointerException

ball.x causes the run-time error NullPointerException. In practice, this is a very common error, and often means you forgot to use new to make a new object for your variable.

Example: A Small Explosion

Lets see an example of how to use multiple sprites in the same program. The following code makes three “fragments” move off from the center of the screen in different directions and at different speeds:

class Sprite {
  float x;
  float y;
  float dx;
  float dy;
}

Sprite frag1 = new Sprite();
Sprite frag2 = new Sprite();
Sprite frag3 = new Sprite();

void setup() {
  size(500, 500);

  smooth();
  noStroke();

  frag1.x = 250;
  frag1.y = 250;
  frag1.dx = -4.2;
  frag1.dy = 3.6;

  frag2.x = 250;
  frag2.y = 250;
  frag2.dx = -2.2;
  frag2.dy = -4.6;

  frag3.x = 250;
  frag3.y = 250;
  frag3.dx = 4.2;
  frag3.dy = 2.6;
}

void draw() {
  background(255);

  fill(255, 0, 0);
  ellipse(frag1.x, frag1.y, 50, 50);

  fill(0, 255, 0);
  ellipse(frag2.x, frag2.y, 50, 50);

  fill(0, 0, 255);
  ellipse(frag3.x, frag3.y, 50, 50);

  frag1.x += frag1.dx;
  frag1.y += frag1.dy;

  frag2.x += frag2.dx;
  frag2.y += frag2.dy;

  frag3.x += frag3.dx;
  frag3.y += frag3.dy;
}

Questions

  1. What are two advantages of using the Sprite class for representing animated objects?

  2. What does the following code fragment print?

    Sprite ball = new Sprite();
    println(ball.x);
    println(ball.y);
    println(ball.dx);
    println(ball.dy);
    
  3. The Sprite class in these notes is for representing 2-dimensional sprites. Write a class called Sprite3d that could be used to represent 3-dimensional sprites.

Programming Questions

  1. Modify the sample program in “a small explosion” so that gravity pulls the particles downwards.