Today we will apply some of the concepts we covered in the previous lessons to add a scrolling background. In addition, we will be adjusting the movement so that it does not "stick" like it used to. Lastly, we will change the player's sprite (character image) depending on his actions.
1. Creating variables that we will be using to manipulate the background.
2. Defining a constructor (which allows us to create objects (instances) with the Background class (remember that classes are blueprints for objects. By instance we refer to an object that was created using a class and therefore has its properties).
3. Creating an update() method that will let us move the background.
4. Adding getters/setters (recall from Day 4 that getters/setters are helper methods that allow us to retrieve and manipulate private variables) that deal with the variables created in step 1.
Let's begin!
I. creating variables
The first two of these represent the x and y coordinates of the background's upper left corner.
So all we need to do is declare (below the class declaration: public class Background { :)
private int bgX, bgY, speedX;
II. defining a constructor
To create these Background objects, we need a Background constructor. So below the variable declaration, we create one. Here, we think ahead about what variables we might need to construct a Background object. When we create new background images, we probably want to do so at a specific location that we choose, so here I will add two parameters - one for the x coordinate, another for the y:
public Background (int x, int y){
bgX = x;
bgY = y;
speedX = 0;
}
The variables bgX, bgY and SpeedX that we defined above will now take the following values: x, y, and 0, respectively. This means that whatever two values that we feed the constructor in the StartingClass to create a Background object will become the values of bgX and bgY. We want the background to start static, so we give it a speed of zero.
III. Update() method
In this lesson we will create an infinitely scrolling background. It will consist of two long images that will consistently loop like this:
1, 2, 1, 2, 1...
Going back to the update() method, let's first list its purposes:
1. It should update the position.
2. If the background is no longer visible, it should either destroy itself (to free up memory) or be recycled by moving to a visible (or soon to be visible) location.
For now, we will be using background images with dimensions 2160 x 480, so remembering what I said about looping (1, 2, 1, 2, 1...) try to conceptualize what this code does:
public void update() {
bgX += speedX;
if (bgX <= -2160){
bgX += 4320;
}
}
Quick Note #2: Be a thoughtful programmer. Think about how things work. Don't just take what I say and accept it without scrutiny. Try to conceptually understand why everything works the way it does. That is the only way you will get better to the point where you can make your own programs. If you have any questions, ask in the comments section. I love answering questions and explaining things in more depth.
bgX += speedX - this will change the bgX by the speed in the x direction. That makes sense.
Now, the if statement: When the x coordinate of the background is at -2160 or below (it is no longer visible), we move it up by 4320 pixels (so that it can go 2160 pixels ahead of the currently visible image. If this is confusing, it will make more sense when we start running this code at the end).
Question: Why does the if statement say: bgX <= -2160 and not bgX == - 2160?
There is a very important reason.
Hint: Think about what would happen if the speed of the Background was not a factor of 2160. Ask yourself these questions:
What is the smallest unit of change in X position?
Can bgX change instantly from 2161 to 2159?
If so, will the condition still be satisfied?
IV. Getters and setters methods
Right click in the code, select Source > Generate Getters and Setters.
It's better to have more than less, so we will generate the pair for all three of them:
Figure 2-22: background.java
public class Background {
private int bgX, bgY, speedX;
public Background(int x, int y){
bgX = x;
bgY = y;
speedX = 0;
}
public void update() {
bgX += speedX;
if (bgX <= -2160){
bgX += 4320;
}
}
public int getBgX() {
return bgX;
}
public int getBgY() {
return bgY;
}
public int getSpeedX() {
return speedX;
}
public void setBgX(int bgX) {
this.bgX = bgX;
}
public void setBgY(int bgY) {
this.bgY = bgY;
}
public void setSpeedX(int speedX) {
this.speedX = speedX;
}
}
We will be dealing with the StartingClass for this lesson, so open that up.
As a general rule:
Whenever we add a new object to our game, we have to do the following:
0. Create a class for it so that we have a blueprint for it. (We did this for our background).
1. Within the StartingClass, we must create objects using this class (first by declaring them as variables below the class declaration and second by assigning them values in the start() method).
2. Within the run() method, we must call the object's update() method.
3. We must paint the new object in the paint() method.
Let's start with number 1 (we finished number 0 above).
I. Declare variable and initialize them
In StartingClass, below the rest of the variables, write the following:
private static Background bg1, bg2;
(We make them static so that we can create getters and setters for them to be used in othe classes for movement).
To initialize them with values:
Scroll down to the start() method, and write ABOVE robot = new Robot();
bg1 = new Background(0, 0);
bg2 = new Background(2160, 0);
This will create two new Background objects separate from each other that you can refer to as bg1 and bg2. Recall that we required two integer values as parameters in the Background constructor, so we feed it x, y coordinates for each newly created Background object.
II. Call the update() method
bg1.update();
bg2.update();
That should do it.
III. paint the background

background.png |
private Image image, character, background;
2. Now below where we defined the character image:
character = getImage(base, "data/character.png");
Define background:
background = getImage(base, "data/background.png");
3. Then scroll to the paint method and add this:
g.drawImage(background, bg1.getBgX(), bg1.getBgY(), this);
g.drawImage(background, bg2.getBgX(), bg2.getBgY(), this);
Images are painted in the order they appear. So if you want the character to be above the background, you need to put these two lines above the line that paints the character!
The resulting StartingClass:
Figure 2-23: StartingClass.java
import java.applet.Applet;
import java.awt.Color;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.net.URL;
public class StartingClass extends Applet implements Runnable, KeyListener {
private Robot robot;
private Image image, character, background;
private Graphics second;
private URL base;
private static Background bg1, bg2;
@Override
public void init() {
setSize(800, 480);
setBackground(Color.BLACK);
setFocusable(true);
addKeyListener(this);
Frame frame = (Frame) this.getParent().getParent();
frame.setTitle("Q-Bot Alpha");
try {
base = getDocumentBase();
} catch (Exception e) {
// TODO: handle exception
}
// Image Setups
character = getImage(base, "data/character.png");
background = getImage(base, "data/background.png");
}
@Override
public void start() {
bg1 = new Background(0,0);
bg2 = new Background(2160, 0);
robot = new Robot();
Thread thread = new Thread(this);
thread.start();
}
@Override
public void stop() {
// TODO Auto-generated method stub
}
@Override
public void destroy() {
// TODO Auto-generated method stub
}
@Override
public void run() {
while (true) {
robot.update();
bg1.update();
bg2.update();
repaint();
try {
Thread.sleep(17);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void update(Graphics g) {
if (image == null) {
image = createImage(this.getWidth(), this.getHeight());
second = image.getGraphics();
}
second.setColor(getBackground());
second.fillRect(0, 0, getWidth(), getHeight());
second.setColor(getForeground());
paint(second);
g.drawImage(image, 0, 0, this);
}
@Override
public void paint(Graphics g) {
g.drawImage(background, bg1.getBgX(), bg1.getBgY(), this);
g.drawImage(background, bg2.getBgX(), bg2.getBgY(), this);
g.drawImage(character, robot.getCenterX() - 61, robot.getCenterY() - 63, this);
}
@Override
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_UP:
System.out.println("Move up");
break;
case KeyEvent.VK_DOWN:
System.out.println("Move down");
break;
case KeyEvent.VK_LEFT:
robot.moveLeft();
break;
case KeyEvent.VK_RIGHT:
robot.moveRight();
break;
case KeyEvent.VK_SPACE:
System.out.println("Jump");
robot.jump();
break;
}
}
@Override
public void keyReleased(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_UP:
System.out.println("Stop moving up");
break;
case KeyEvent.VK_DOWN:
System.out.println("Stop moving down");
break;
case KeyEvent.VK_LEFT:
robot.stop();
break;
case KeyEvent.VK_RIGHT:
robot.stop();
break;
case KeyEvent.VK_SPACE:
System.out.println("Stop jumping");
break;
}
}
@Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
public static Background getBg1() {
return bg1;
}
public static Background getBg2() {
return bg2;
}
}
The strategy to fixing this simple. We will keep boolean variables that keep track of whether each directional arrow is down, and when one is released, we will check if the other one is pressed before we stop or continue moving the opposite direction.
Also, we will be allowing the character to duck, and will prevent movement while ducked.
As I feel like all the changes I made to the Robot class can be understood when you examine them line by line, I will now paste the full Robot class here, instead of showing its creation step by step. If you have any specific questions, do let me know.
Making Necessary Changes to Robot class
Figure 2-24: Robot.java
import java.awt.Graphics;
public class Robot {
// Constants are Here
final int JUMPSPEED = -15;
final int MOVESPEED = 5;
final int GROUND = 382;
private int centerX = 100;
private int centerY = GROUND;
private boolean jumped = false;
private boolean movingLeft = false;
private boolean movingRight = false;
private boolean ducked = false;
Private static Background bg1 = StartingClass.getBg1();
private static Background bg2 = StartingClass.getBg2();
private int speedX = 0;
private int speedY = 1;
public void update() {
// Moves Character or Scrolls Background accordingly.
if (speedX < 0) {
centerX += speedX;
}
if (speedX == 0 || speedX < 0) {
bg1.setSpeedX(0);
bg2.setSpeedX(0);
}
if (centerX <= 200 && speedX > 0) {
centerX += speedX;
}
if (speedX > 0 && centerX > 200){
bg1.setSpeedX(-MOVESPEED);
bg2.setSpeedX(-MOVESPEED);
}
// Updates Y Position
centerY += speedY;
if (centerY + speedY >= GROUND) {
centerY = GROUND;
}
// Handles Jumping
if (jumped == true) {
speedY += 1;
if (centerY + speedY >= GROUND) {
centerY = GROUND;
speedY = 0;
jumped = false;
}
}
// Prevents going beyond X coordinate of 0
if (centerX + speedX <= 60) {
centerX = 61;
}
}
public void moveRight() {
if (ducked == false) {
speedX = MOVESPEED;
}
}
public void moveLeft() {
if (ducked == false) {
speedX = -MOVESPEED;
}
}
public void stopRight() {
setMovingRight(false);
stop();
}
public void stopLeft() {
setMovingLeft(false);
stop();
}
private void stop() {
if (isMovingRight() == false && isMovingLeft() == false) {
speedX = 0;
}
if (isMovingRight() == false && isMovingLeft() == true) {
moveLeft();
}
if (isMovingRight() == true && isMovingLeft() == false) {
moveRight();
}
}
public void jump() {
if (jumped == false) {
speedY = JUMPSPEED;
jumped = true;
}
}
public int getCenterX() {
return centerX;
}
public int getCenterY() {
return centerY;
}
public boolean isJumped() {
return jumped;
}
public int getSpeedX() {
return speedX;
}
public int getSpeedY() {
return speedY;
}
public void setCenterX(int centerX) {
this.centerX = centerX;
}
public void setCenterY(int centerY) {
this.centerY = centerY;
}
public void setJumped(boolean jumped) {
this.jumped = jumped;
}
public void setSpeedX(int speedX) {
this.speedX = speedX;
}
public void setSpeedY(int speedY) {
this.speedY = speedY;
}
public boolean isDucked() {
return ducked;
}
public void setDucked(boolean ducked) {
this.ducked = ducked;
}
public boolean isMovingRight() {
return movingRight;
}
public void setMovingRight(boolean movingRight) {
this.movingRight = movingRight;
}
public boolean isMovingLeft() {
return movingLeft;
}
public void setMovingLeft(boolean movingLeft) {
this.movingLeft = movingLeft;
}
}
1. I never talked about constants before, so let me spend a few minutes talking about them.
Constants are types of variables. They just are variables that have permanent values.
To indicate a constant, we just use the keyword final (which cannot be used as a variable name for this reason), and by convention (not law) we CAPITALIZE ITS NAME.
The lines:
final int JUMPSPEED = -15;
final int MOVESPEED = 5;
final int GROUND = 382;
Are three constants. Previously, I had these hard-coded, but creating constants for each of these values makes it easy to make changes to some of the basics of the game. In each location that I used -15, 5, and 382, I replaced the integers with these constants.
2. I replaced most of the System.out.println() placeholders with functional code. Where we previously indicated the background would scroll or stop, I added code that does exactly that.
3. The booleans are there to keep track of movement. Every time that you press a keyboard button, these booleans will change. When you are holding down a keyboard button, a boolean (movingLeft or movingRight) will keep track of that.
For example: if I am holding down the left button (movingLeft == true) and I also press the right button (movingRight == true) and then release either one of those, previously the character would've stopped (as we would've called the stop method); however, now, it will check if either direction is still being held and choose from two possibilities: 1. stop the character if the other direction's button is not down. 2. if the other direction's button is down, move that way.
4. You will get errors in the StartingClass when you replace your Robot.java with the code above. We will fix these soon, so do not panic!
Making Necessary Changes to StartingClass
keyPressed() and keyReleased().
This is because our character movement occurs here.
Replace the two methods with the below code:
figure 2-25: keypressed() and keyreleased()
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_UP:
System.out.println("Move up");
break;
case KeyEvent.VK_DOWN:
currentSprite = characterDown;
if (robot.isJumped() == false){
robot.setDucked(true);
robot.setSpeedX(0);
}
break;
case KeyEvent.VK_LEFT:
robot.moveLeft();
robot.setMovingLeft(true);
break;
case KeyEvent.VK_RIGHT:
robot.moveRight();
robot.setMovingRight(true);
break;
case KeyEvent.VK_SPACE:
robot.jump();
break;
}
}
@Override
public void keyReleased(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_UP:
System.out.println("Stop moving up");
break;
case KeyEvent.VK_DOWN:
currentSprite = character;
robot.setDucked(false);
break;
case KeyEvent.VK_LEFT:
robot.stopLeft();
break;
case KeyEvent.VK_RIGHT:
robot.stopRight();
break;
case KeyEvent.VK_SPACE:
break;
}
}
![]()
| ![]()
| ![]()
|
1. We want to create a Image object for the down and jumped png's (we already did character.png), so add characterDown and characterJumped to the variable declaration statement like so:
"private Image image, currentSprite, character, characterDown, characterJumped, background;"
Note: Order of the declaration does not matter, but I like to organize them.
What is currentSprite, you ask? At the present, we are painting the Robot robot (Robot object named robot) with the image character, but we now want to paint currentSprite, which will dynamically change to character, characterDown or characterJumped according to what the Robot robot is doing.
2. Next, update the // Image Setups section of the code:
character = getImage(base, "data/character.png");
characterDown = getImage(base, "data/down.png");
characterJumped = getImage(base, "data/jumped.png");
currentSprite = character;
background = getImage(base, "data/background.png");
3. Make the following changes (in bold) to the run() method:
Figure 2-26: run() method in startingclass
public void run() {
while (true) {
robot.update();
if (robot.isJumped()){
currentSprite = characterJumped;
}else if (robot.isJumped() == false && robot.isDucked() == false){
currentSprite = character;
}
bg1.update();
bg2.update();
repaint();
try {
Thread.sleep(17);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
4. The last thing you need to do is change the Image variable that is being painted for the robot:
g.drawImage(character, robot.getCenterX() - 61, robot.getCenterY() - 63, this);
should now be:
g.drawImage(currentSprite, robot.getCenterX() - 61, robot.getCenterY() - 63, this);
That should fix all the problems in your code.
One last thing, add these getters/setters to the end of the class(it might be easier manually):
return bg1;
}
public static Background getBg2() {
return bg2;
}
and we have the final StartingClass:
FIGURE 2-27: STARTINGCLASS, end of day 5
import java.applet.Applet;
import java.awt.Color;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.net.URL;
public class StartingClass extends Applet implements Runnable, KeyListener {
private Robot robot;
private Image image, currentSprite, character, characterDown, characterJumped, background;
private Graphics second;
private URL base;
private static Background bg1, bg2;
@Override
public void init() {
setSize(800, 480);
setBackground(Color.BLACK);
setFocusable(true);
addKeyListener(this);
Frame frame = (Frame) this.getParent().getParent();
frame.setTitle("Q-Bot Alpha");
try {
base = getDocumentBase();
} catch (Exception e) {
// TODO: handle exception
}
// Image Setups
character = getImage(base, "data/character.png");
characterDown = getImage(base, "data/down.png");
characterJumped = getImage(base, "data/jumped.png");
currentSprite = character;
background = getImage(base, "data/background.png");
}
@Override
public void start() {
bg1 = new Background(0,0);
bg2 = new Background(2160, 0); robot = new Robot();
Thread thread = new Thread(this);
thread.start();
}
@Override
public void stop() {
// TODO Auto-generated method stub
}
@Override
public void destroy() {
// TODO Auto-generated method stub
}
@Override
public void run() {
while (true) {
robot.update();
if (robot.isJumped()){
currentSprite = characterJumped;
}else if (robot.isJumped() == false && robot.isDucked() == false){
currentSprite = character;
}
bg1.update();
bg2.update();
repaint();
try {
Thread.sleep(17);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void update(Graphics g) {
if (image == null) {
image = createImage(this.getWidth(), this.getHeight());
second = image.getGraphics();
}
second.setColor(getBackground());
second.fillRect(0, 0, getWidth(), getHeight());
second.setColor(getForeground());
paint(second);
g.drawImage(image, 0, 0, this);
}
@Override
public void paint(Graphics g) {
g.drawImage(background, bg1.getBgX(), bg1.getBgY(), this);
g.drawImage(background, bg2.getBgX(), bg2.getBgY(), this);
g.drawImage(currentSprite, robot.getCenterX() - 61, robot.getCenterY() - 63, this);
}
@Override
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_UP:
System.out.println("Move up");
break;
case KeyEvent.VK_DOWN:
currentSprite = characterDown;
if (robot.isJumped() == false){
robot.setDucked(true);
robot.setSpeedX(0);
}
break;
case KeyEvent.VK_LEFT:
robot.moveLeft();
robot.setMovingLeft(true);
break;
case KeyEvent.VK_RIGHT:
robot.moveRight();
robot.setMovingRight(true);
break;
case KeyEvent.VK_SPACE:
robot.jump();
break;
}
}
@Override
public void keyReleased(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_UP:
System.out.println("Stop moving up");
break;
case KeyEvent.VK_DOWN:
currentSprite = character;
robot.setDucked(false);
break;
case KeyEvent.VK_LEFT:
robot.stopLeft();
break;
case KeyEvent.VK_RIGHT:
robot.stopRight();
break;
case KeyEvent.VK_SPACE:
break;
}
}
@Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
public static Background getBg1() {
return bg1;
}
public static Background getBg2() {
return bg2;
}
}
That's it for Day 5! I hope that you guys learned a lot and that I did not proceed too quickly.
Thank you for reading my tutorials and supporting Kilobolt Studios!
Got questions? Ask away!

kiloboltgameday5.zip |