22 min read

“Flying is learning how to throw yourself at the ground and miss.”

                                                                                             – Douglas Adams

In this article by Michael Haungs, author of the book Creative Greenfoot, we will create a simple game using basic movements in Greenfoot.

Actors in creative Greenfoot applications, such as games and animations, often have movement that can best be described as being launched. For example, a soccer ball, bullet, laser, light ray, baseball, and firework are examples of this type of object. One common method of implementing this type of movement is to create a set of classes that model real-world physical properties (mass, velocity, acceleration, friction, and so on) and have game or simulation actors inherit from these classes. Some refer to this as creating a physics engine for your game or simulation. However, this course of action is complex and often overkill. There are often simple heuristics we can use to approximate realistic motion. This is the approach we will take here.

In this article, you will learn about the basics of projectiles, how to make an object bounce, and a little about particle effects. We will apply what you learn to a small platform game that we will build up over the course of this article.

Creating realistic flying objects is not simple, but we will cover this topic in a methodical, step-by-step approach, and when we are done, you will be able to populate your creative scenarios with a wide variety of flying, jumping, and launched objects. It’s not as simple as Douglas Adams makes it sound in his quote, but nothing worth learning ever is.

(For more resources related to this topic, see here.)

Cupcake Counter

It is beneficial to the learning process to discuss topics in the context of complete scenarios. Doing this forces us to handle issues that might be elided in smaller, one-off examples. In this article, we will build a simple platform game called Cupcake Counter (shown in Figure 1). We will first look at a majority of the code for the World and Actor classes in this game without showing the code implementing the topic of this article, that is, the different forms of projectile-based movement.

Creative Greenfoot

Figure 1: This is a screenshot of Cupcake Counter

How to play

The goal of Cupcake Counter is to collect as many cupcakes as you can before being hit by either a ball or a fountain. The left and right arrow keys move your character left and right and the up arrow key makes your character jump. You can also use the space bar key to jump. After touching a cupcake, it will disappear and reappear randomly on another platform. Balls will be fired from the turret at the top of the screen and fountains will appear periodically. The game will increase in difficulty as your cupcake count goes up. The game requires good jumping and avoiding skills.

Implementing Cupcake Counter

Create a scenario called Cupcake Counter and add each class to it as they are discussed.

The CupcakeWorld class

This subclass of World sets up all the actors associated with the scenario, including a score. It is also responsible for generating periodic enemies, generating rewards, and increasing the difficulty of the game over time. The following is the code for this class:

import greenfoot.*;
import java.util.List;
 
public class CupcakeWorld extends World {
private Counter score;
private Turret turret;
public int BCOUNT = 200;
private int ballCounter = BCOUNT;
public int FCOUNT = 400;
private int fountainCounter = FCOUNT;
private int level = 0;
public CupcakeWorld() {
   super(600, 400, 1, false);
   setPaintOrder(Counter.class, Turret.class, Fountain.class,
   Jumper.class, Enemy.class, Reward.class, Platform.class);
   prepare();
}
public void act() {
   checkLevel();
}
private void checkLevel() {
   if( level > 1 ) generateBalls();
   if( level > 4 ) generateFountains();
   if( level % 3 == 0 ) {
     FCOUNT--;
     BCOUNT--;
     level++;
   }
}
private void generateFountains() {
   fountainCounter--;
   if( fountainCounter < 0 ) {
     List<Brick> bricks = getObjects(Brick.class);
     int idx = Greenfoot.getRandomNumber(bricks.size());
     Fountain f = new Fountain();
     int top = f.getImage().getHeight()/2 + bricks.get(idx).getImage().getHeight()/2;
     addObject(f, bricks.get(idx).getX(),
     bricks.get(idx).getY()-top);
     fountainCounter = FCOUNT;
   }
}
private void generateBalls() {
   ballCounter--;
   if( ballCounter < 0 ) {
     Ball b = new Ball();
     turret.setRotation(15 * -b.getXVelocity());
     addObject(b, getWidth()/2, 0);
     ballCounter = BCOUNT;
   }
}
public void addCupcakeCount(int num) {
   score.setValue(score.getValue() + num);
   generateNewCupcake();
}
private void generateNewCupcake() {
   List<Brick> bricks = getObjects(Brick.class);
   int idx = Greenfoot.getRandomNumber(bricks.size());
   Cupcake cake = new Cupcake();
   int top = cake.getImage().getHeight()/2 +
   bricks.get(idx).getImage().getHeight()/2;
   addObject(cake, bricks.get(idx).getX(),
   bricks.get(idx).getY()-top);
}
public void addObjectNudge(Actor a, int x, int y) {
   int nudge = Greenfoot.getRandomNumber(8) - 4;
   super.addObject(a, x + nudge, y + nudge);
}
private void prepare(){
   // Add Bob
   Bob bob = new Bob();
   addObject(bob, 43, 340);
   // Add floor
   BrickWall brickwall = new BrickWall();
   addObject(brickwall, 184, 400);
   BrickWall brickwall2 = new BrickWall();
   addObject(brickwall2, 567, 400);
   // Add Score
   score = new Counter();
   addObject(score, 62, 27);
   // Add turret
   turret = new Turret();
   addObject(turret, getWidth()/2, 0);
   // Add cupcake
   Cupcake cupcake = new Cupcake();
   addObject(cupcake, 450, 30);
   // Add platforms
   for(int i=0; i<5; i++) {
     for(int j=0; j<6; j++) {
       int stagger = (i % 2 == 0 ) ? 24 : -24;
       Brick brick = new Brick();
       addObjectNudge(brick, stagger + (j+1)*85, (i+1)*62);
     }
   }
}
}

Let’s discuss the methods in this class in order. First, we have the class constructor CupcakeWorld(). After calling the constructor of the superclass, it calls setPaintOrder() to set the actors that will appear in front of other actors when displayed on the screen. The main reason why we use it here, is so that no actor will cover up the Counter class, which is used to display the score. Next, the constructor method calls prepare() to add and place the initial actors into the scenario.

Inside the act() method, we will only call the function checkLevel(). As the player scores points in the game, the level variable of the game will also increase. The checkLevel() function will change the game a bit according to its level variable. When our game first starts, no enemies are generated and the player can easily get the cupcake (the reward). This gives the player a chance to get accustomed to jumping on platforms. As the cupcake count goes up, balls and fountains will be added. As the level continues to rise, checkLevel() reduces the delay between creating balls (BCOUNT) and fountains (FCOUNT). The level variable of the game is increased in the addCupcakeCount() method.

The generateFountains() method adds a Fountain actor to the scenario. The rate at which we create fountains is controlled by the delay variable fountainContainer. After the delay, we create a fountain on a randomly chosen Brick (the platforms in our game). The getObjects() method returns all of the actors of a given class presently in the scenario. We then use getRandomNumber() to randomly choose a number between one and the number of Brick actors. Next, we use addObject() to place the new Fountain object on the randomly chosen Brick object.

Generating balls using the generateBalls() method is a little easier than generating fountains. All balls are created in the same location as the turret at the top of the screen and sent from there with a randomly chosen trajectory. The rate at which we generate new Ball actors is defined by the delay variable ballCounter. Once we create a Ball actor, we rotate the turret based on its x velocity. By doing this, we create the illusion that the turret is aiming and then firing Ball Actor. Last, we place the newly created Ball actor into the scenario using the addObject() method.

The addCupcakeCount() method is called by the actor representing the player (Bob) every time the player collides with Cupcake. In this method, we increase score and then call generateNewCupcake() to add a new Cupcake actor to the scenario. The generateNewCupcake() method is very similar to generateFountains(), except for the lack of a delay variable, and it randomly places Cupcake on one of the bricks instead of a Fountain actor.

In all of our previous scenarios, we used a prepare() method to add actors to the scenario. The major difference between this prepare() method and the previous ones, is that we use the addObjectNudge() method instead of addObject() to place our platforms. The addObjectNudge() method simply adds a little randomness to the placement of the platforms, so that every new game is a little different. The random variation in the platforms will cause the Ball actors to have different bounce patterns and require the player to jump and move a bit more carefully. In the call to addObjectNudge(), you will notice that we used the numbers 85 and 62. These are simply numbers that spread the platforms out appropriately, and they were discovered through trial and error.

I created a blue gradient background to use for the image of CupcakeWorld.

Enemies

In Cupcake Counter, all of the actors that can end the game if collided with are subclasses of the Enemy class. Using inheritance is a great way to share code and reduce redundancy for a group of similar actors. However, we often will create class hierarchies in Greenfoot solely for polymorphism. Polymorphism refers to the ability of a class in an object-orientated language to take on many forms. We are going to use it, so that our player actor only has to check for collision with an Enemy class and not every specific type of Enemy, such as Ball or RedBall. Also, by coding this way, we are making it very easy to add code for additional enemies, and if we find that our enemies have redundant code, we can easily move that code into our Enemy class. In other words, we are making our code extensible and maintainable.

Here is the code for our Enemy class:

import greenfoot.*;
 
public abstract class Enemy extends Actor {
}

The Ball class extends the Enemy class. Since Enemy is solely used for polymorphism, the Ball class contains all of the code necessary to implement bouncing and an initial trajectory. Here is the code for this class:

import greenfoot.*;
 
public class Ball extends Enemy {
protected int actorHeight;
private int speedX = 0;
public Ball() {
   actorHeight = getImage().getHeight();
   speedX = Greenfoot.getRandomNumber(8) - 4;
   if( speedX == 0 ) {
     speedX = Greenfoot.getRandomNumber(100) < 50 ? -1 : 1;
   }
}
public void act() {
   checkOffScreen();
}
public int getXVelocity() {
   return speedX;
}
private void checkOffScreen() {
   if( getX() < -20 || getX() > getWorld().getWidth() + 20 ) {
     getWorld().removeObject(this);
   } else if( getY() > getWorld().getHeight() + 20 ) {
     getWorld().removeObject(this);
   }
}
}

The implementation of Ball is missing the code to handle moving and bouncing. As we stated earlier, we will go over all the projectile-based code after providing the code we are using as the starting point for this game. In the Ball constructor, we randomly choose a speed in the x direction and save it in the speedX instance variable. We have included one accessory method to return the value of speedX (getXVelocity()). Last, we include checkOffScreen() to remove Ball once it goes off screen. If we do not do this, we would have a form of memory leak in our application because Greenfoot will continue to allocate resources and manage any actor until it is removed from the scenario. For the Ball class, I choose to use the ball.png image, which comes with the standard installation of Greenfoot.

In this article, we will learn how to create a simple particle effect. Creating an effect is more about the use of a particle as opposed to its implementation. In the following code, we create a generic particle class, Particles, that we will extend to create a RedBall particle. We have organized the code in this way to easily accommodate adding particles in the future. Here is the code:

import greenfoot.*;
 
public class Particles extends Enemy {
private int turnRate = 2;
private int speed = 5;
private int lifeSpan = 50;
public Particles(int tr, int s, int l) {
   turnRate = tr;
   speed = s;
   lifeSpan = l;
   setRotation(-90);
}
public void act() {
   move();
   remove();
}
private void move() {
   move(speed);
   turn(turnRate);
}
private void remove() {
   lifeSpan--;
   if( lifeSpan < 0 ) {
     getWorld().removeObject(this);
   }
}
}

Our particles are implemented to move up and slightly turn each call of the act() method. A particle will move lifeSpan times and then remove itself. As you might have guessed, lifeSpan is another use of a delay variable. The turnRate property can be either positive (to turn slightly right) or negative (to turn slightly left).

We only have one subclass of Particles, RedBall. This class supplies the correct image for RedBall, supplies the required input for the Particles constructor, and then scales the image according to the parameters scaleX and scaleY. Here’s the implementation:

import greenfoot.*;
 
public class RedBall extends Particles {
public RedBall(int tr, int s, int l, int scaleX, int scaleY) {
   super(tr, s, l);
   getImage().scale(scaleX, scaleY);
}
}

For RedBall, I used the Greenfoot-supplied image red-draught.png.

Fountains

In this game, fountains add a unique challenge. After reaching level five (see the World class CupcakeWorld), Fountain objects will be generated and randomly placed in the game. Figure 2 shows a fountain in action. A Fountain object continually spurts RedBall objects into the air like water from a fountain.

Creative Greenfoot

Figure 2: This is a close-up of a Fountain object in the game Cupcake Counter

Let’s take a look at the code that implements the Fountain class:

import greenfoot.*;
import java.awt.Color;
 
public class Fountain extends Actor {
private int lifespan = 75;
private int startDelay = 100;
private GreenfootImage img;
public Fountain() {
   img = new GreenfootImage(20,20);
   img.setColor(Color.blue);
   img.setTransparency(100);
   img.fill();
   setImage(img);
}
public void act() {
   if( --startDelay == 0 ) wipeView();
   if( startDelay < 0 ) createRedBallShower();
}
private void wipeView() {
   img.clear();
}
private void createRedBallShower() {
}
}

The constructor for Fountain creates a new blue, semitransparent square and sets that to be its image. We start with a blue square to give the player of the game a warning that a fountain is about to erupt. Since fountains are randomly placed at any location, it would be unfair to just drop one on our player and instantly end the game. This is also why RedBall is a subclass of Enemy and Fountain is not. It is safe for the player to touch the blue square. The startDelay delay variable is used to pause for a short amount of time, then remove the blue square (using the function wipeView()), and then start the RedBall shower (using the createRedBallShower() function). We can see this in the act() method.

Turrets

In the game, there is a turret in the top-middle of the screen that shoots purple bouncy balls at the player. It is shown in Figure 1. Why do we use a bouncy-ball shooting turret? Because this is our game and we can! The implementation of the Turret class is very simple. Most of the functionality of rotating the turret and creating Ball to shoot is handled by CupcakeWorld in the generateBalls() method already discussed. The main purpose of this class is to just draw the initial image of the turret, which consists of a black circle for the base of the turret and a black rectangle to serve as the cannon. Here is the code:

import greenfoot.*;
import java.awt.Color;
 
public class Turret extends Actor {
private GreenfootImage turret;
private GreenfootImage gun;
private GreenfootImage img;
public Turret() {
   turret = new GreenfootImage(30,30);
   turret.setColor(Color.black);
   turret.fillOval(0,0,30,30);
  
   gun = new GreenfootImage(40,40);
   gun.setColor(Color.black);
   gun.fillRect(0,0,10,35);
  
   img = new GreenfootImage(60,60);
   img.drawImage(turret, 15, 15);
   img.drawImage(gun, 25, 30);
   img.rotate(0);
  
   setImage(img);
}
}

We previously talked about the GreenfootImage class and how to use some of its methods to do custom drawing. One new function we introduced is drawImage(). This method allows you to draw one GreenfootImage into another. This is how you compose images, and we used it to create our turret from a rectangle image and a circle image.

Rewards

We create a Reward class for the same reason we created an Enemy class. We are setting ourselves up to easily add new rewards in the future. Here is the code:

import greenfoot.*;
 
public abstract class Reward extends Actor {
}

The Cupcake class is a subclass of the Reward class and represents the object on the screen the player is constantly trying to collect. However, cupcakes have no actions to perform or state to keep track of; therefore, its implementation is simple:

import greenfoot.*;
 
public class Cupcake extends Reward {
}

When creating this class, I set its image to be muffin.png. This is an image that comes with Greenfoot. Even though the name of the image is a muffin, it still looks like a cupcake to me.

Jumpers

The Jumper class is a class that will allow all subclasses of it to jump when pressing either the up arrow key or the spacebar. At this point, we just provide a placeholder implementation:

import greenfoot.*;
 
public abstract class Jumper extends Actor
{
protected int actorHeight;
public Jumper() {
   actorHeight = getImage().getHeight();
}
public void act() {
   handleKeyPresses();
}
protected void handleKeyPresses() {
}
}

The next class we are going to present is the Bob class. The Bob class extends the Jumper class and then adds functionality to let the player move it left and right. Here is the code:

import greenfoot.*;
 
public class Bob extends Jumper {
private int speed = 3;
private int animationDelay = 0;
private int frame = 0;
private GreenfootImage[] leftImages;
private GreenfootImage[] rightImages;
private int actorWidth;
private static final int DELAY = 3;
public Bob() {
   super();
  
   rightImages = new GreenfootImage[5];
   leftImages = new GreenfootImage[5];
  
   for( int i=0; i<5; i++ ) {
     rightImages[i] = new GreenfootImage("images/Dawson_Sprite_Sheet_0" + Integer.toString(3+i) + ".png");
     leftImages[i] = new GreenfootImage(rightImages[i]);
     leftImages[i].mirrorHorizontally();
   }
  
   actorWidth = getImage().getWidth();
}
public void act() {
   super.act();
   checkDead();
   eatReward();
}
private void checkDead() {
   Actor enemy = getOneIntersectingObject(Enemy.class);
   if( enemy != null ) {
     endGame();
   }
}
private void endGame() {
   Greenfoot.stop();
}
private void eatReward() {
   Cupcake c = (Cupcake) getOneIntersectingObject(Cupcake.class);
   if( c != null ) {
     CupcakeWorld rw = (CupcakeWorld) getWorld();
     rw.removeObject(c);
     rw.addCupcakeCount(1);
   }
}
// Called by superclass
protected void handleKeyPresses() {
   super.handleKeyPresses();
  
   if( Greenfoot.isKeyDown("left") ) {
     if( canMoveLeft() ) {moveLeft();}
   }
   if( Greenfoot.isKeyDown("right") ) {
     if( canMoveRight() ) {moveRight();}
   }
}
private boolean canMoveLeft() {
   if( getX() < 5 ) return false;
   return true;
}
private void moveLeft() {
   setLocation(getX() - speed, getY());
   if( animationDelay % DELAY == 0 ) {
     animateLeft();
     animationDelay = 0;
   }
   animationDelay++;
}
private void animateLeft() {
   setImage( leftImages[frame++]);
   frame = frame % 5;
   actorWidth = getImage().getWidth();
}
private boolean canMoveRight() {
   if( getX() > getWorld().getWidth() - 5) return false;
   return true;
}
private void moveRight() {
   setLocation(getX() + speed, getY());
   if( animationDelay % DELAY == 0 ) {
     animateRight();
     animationDelay = 0;
   }
   animationDelay++;
}
private void animateRight() {
   setImage( rightImages[frame++]);
   frame = frame % 5;
   actorWidth = getImage().getWidth();
}
}

Like CupcakeWorld, this class is substantial. We will discuss each method it contains sequentially. First, the constructor’s main duty is to set up the images for the walking animation. The images came from www.wikia.com and were supplied, in the form of a sprite sheet, by the user Mecha Mario. A direct link to the sprite sheet is http://smbz.wikia.com/wiki/File:Dawson_Sprite_Sheet.PNG. Note that I manually copied and pasted the images I used from this sprite sheet using my favorite image editor.

Free Internet resources

Unless you are also an artist or a musician in addition to being a programmer, you are going to be hard pressed to create all of the assets you need for your Greenfoot scenario. If you look at the credits for AAA video games, you will see that the number of artists and musicians actually equal or even outnumber the programmers.

Luckily, the Internet comes to the rescue. There are a number of websites that supply legally free assets you can use. For example, the website I used to get the images for the Bob class supplies free content under the Creative Commons Attribution-Share Alike License 3.0 (Unported) (CC-BY-SA) license. It is very important that you check the licensing used for any asset you download off the Internet and follow those user agreements carefully. In addition, make sure that you fully credit the source of your assets. For games, you should include a Credits screen to cite all the sources for the assets you used.

The following are some good sites for free, online assets:

www.wikia.com

newgrounds.com

http://incompetech.com

opengameart.org

untamed.wild-refuge.net/rpgxp.php

Next, we have the act() method. It first calls the act() method of its superclass. It needs to do this so that we get the jumping functionality that is supplied by the Jumper class. Then, we call checkDead() and eatReward(). The checkDead()method ends the game if this instance of the Bob class touches an enemy, and eatReward() adds one to our score, by calling the CupcakeWorld method addCupcakeCount(), every time it touches an instance of the Cupcake class.

The rest of the class implements moving left and right. The main method for this is handleKeyPresses(). Like in act(), the first thing we do, is call handleKeyPresses() contained in the Jumper superclass. This runs the code in Jumper that handles the spacebar and up arrow key presses. The key to handling key presses is the Greenfoot method isKeyDown() (see the following information box). We use this method to check if the left arrow or right arrow keys are presently being pressed. If so, we check whether or not the actor can move left or right using the methods canMoveLeft() and canMoveRight(), respectively. If the actor can move, we then call either moveLeft() or moveRight().

Handling key presses in Greenfoot

The second tutorial explains how to control actors with the keyboard. To refresh your memory, we are going to present some information on the keyboard control here.

The primary method we use in implementing keyboard control is isKeyDown(). This method provides a simple way to check whether a certain key is being pressed. Here is an excerpt from Greenfoot’s documentation:

public static boolean isKeyDown(java.lang.String keyName)
Check whether a given key is currently pressed down.
 
Parameters:
keyName:This is the name of the key to check.
 
This returns : true if the key is down.
 
Using isKeyDown() is easy. The ease of capturing and using input is one of the major strengths of Greenfoot. 
Here is example code that will pause the execution of the game if the "p" key is pressed:
 
if( Greenfoot.isKeyDown("p") {
Greenfoot.stop();
}

Next, we will discuss canMoveLeft(), moveLeft(), and animateLeft(). The canMoveRight(), moveRight(), and animateRight()methods mirror their functionality and will not be discussed. The sole purpose of canMoveLeft() is to prevent the actor from walking off the left-hand side of the screen. The moveLeft() method moves the actor using setLocation() and then animates the actor to look as though it is moving to the left-hand side. It uses a delay variable to make the walking speed look natural (not too fast). The animateLeft() method sequentially displays the walking-left images.

Platforms

The game contains several platforms that the player can jump or stand on. The platforms perform no actions and only serve as placeholders for images. We use inheritance to simplify collision detection. Here is the implementation of Platform:

import greenfoot.*;
 
public class Platform extends Actor {
}

Here’s the implementation of BrickWall:

import greenfoot.*;
 
public class BrickWall extends Platform {
}

Here’s the implementation of Brick:

import greenfoot.*;
 
public class Brick extends Platform {
}

You should now be able to compile and test Cupcake Counter. Make sure you handle any typos or other errors you introduced while inputting the code. For now, you can only move left and right.

Summary

We have created a simple game using basic movements in Greenfoot.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here