Simple Game Tutorial

<< Battleship | FinalProjectsTrailIndex | BoxMover >>

To help with your final project, this tutorial will guide you through the process of making a game applet that can run on a web page from a jar (Java archive) file. We will first load the art and sound resources, then implement keys to move the player, add a timer to make and move enemies, and then finally add collision detection. The final game will have Chicken Little avoid falling bits of sky. Click on the image below to see a video that demonstrates the game.

Stage 1: Loading art and sound

Stage 1 Video

Feel free to use your own sounds and art, but here are some I've made using GIMP and Audacity for you to use:

squish.wav

bell.wav

zip file with resources

Stage1.java

import java.applet.*;
import java.awt.*;
import java.awt.image.*;
import java.io.*;

import javax.imageio.ImageIO;


public class Stage1 extends Applet {
	public static int BOTTOM=580, TOP=0, LEFT=0, RIGHT=800;

	private BufferedImage sky, chickImg, cloudImg;
	private AudioClip bell,squish;

	private int problems=0;

	public void loadResources(){
		//We load the large files once to save time,
		//getting the resource from the jar file
		try {
				cloudImg = ImageIO.read( getClass().getResource("/Cloud.png"));
				chickImg = ImageIO.read( getClass().getResource("/Chick.png"));
				sky = ImageIO.read( getClass().getResource("/sky.png"));
				bell = getAudioClip(getClass().getResource("/bell.wav"));
				squish = getAudioClip(getClass().getResource("/squish.wav"));
				// to use: squish.play();
		} catch (Exception e) {

		}

	}
	public void init(){
		this.loadResources();
		if (squish !=null)
			squish.play();//for testing to see if the sound loaded
        repaint();
	}

	public String nullReport(Object o, String s){
		if(o==null){
			problems++;
			return s+" is null";
		}
		return s+" is not null";
	}
	public void paint(Graphics g){
		if (sky!=null) g.drawImage(sky, LEFT, TOP, null);
		if (cloudImg!=null) g.drawImage(cloudImg, (RIGHT-LEFT-100)/2, TOP, 100,40,null); //for testing
		if (chickImg!=null) g.drawImage(chickImg, (RIGHT-LEFT-60)/2, BOTTOM-60, 60, 80 ,null); //for testing
		g.drawString(nullReport(bell,"bell"), 20, 20);
		g.drawString(nullReport(squish, "squish"), 20, 35);
		g.drawString(nullReport(sky, "sky"), 20, 50);
		g.drawString(nullReport(cloudImg, "cloudIimg"), 20, 65);
		g.drawString(nullReport(chickImg, "chickImg"), 20, 80);
		g.drawString(problems+" problems found", 20, 100);
		problems=0;
	}
}

If you use BlueJ, put the sound and graphic files in the Project Folder

If you use Eclipse:

  1. Make sure in your Project Preferences>Java Build Path that the source and output folder is the Project folder. If you wish to use separate folders you may have to change the code so that the graphics and sounds point to the correct directory. It is easier for now to put everything in the same folder the way BlueJ does.You can drag your sound and graphics files from the desktop into your project's src folder. If they are put their manually, you can make sure that they are registered and packed into your jar file by going to the project's "Properties" (Ctrl-Enter or Cmd-I), Select "Java Build Path" on the left, select the "Libraries" tab on the right, then press the "Add Class Folder" button to select the folder that has your resources. You should see the .png files and the .wav files listed. After pressing OK a few times, you may notice the the folder will have a different mini-icon and a "Referenced Libraries" folder if you have separate folders in your project.
  2. You may wish to also edit your Run/Debug Settings>ChickenLittleApplet[Parameters] tab has width:800, Height:600 so you don't have to resize it every time you run the Applet.

Stage 2: Making a player and Using Key input

Chick.java

import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;


public class Chick {
	private static final int RIGHT = 800;
	public static final int WIDTH=60, HEIGHT=80;
	private BufferedImage img;
	private Rectangle box;
	private int bottom, life;
	private double dx, dy;
	private boolean faceRight;
	private boolean jumping;

	public Chick (BufferedImage img, int x, int y){
		this.img = img;
		box = new Rectangle (x, y-HEIGHT, WIDTH,HEIGHT);
		dx=0D;
		dy=0D;
		bottom=y-HEIGHT;
		faceRight=true;
		jumping=false;
		life=100;
	}

	public void draw(Graphics g) {

		if(life <= 0) return;
		move();
		if(!faceRight){
			g.drawImage(img, box.x, box.y, 
					box.x+box.width, box.y+box.height, 
					0, 0, img.getWidth(), img.getHeight(), null);
		}else{
			g.drawImage(img, box.x, box.y, 
					box.x+box.width, box.y+box.height, 
					img.getWidth(), 0, 0, img.getHeight(), null);
		}	

	}
	public void hit(int amt){
		life-=amt;
		if(life<0) life=0;
	}
	public void setDx(double amt){
		dx=amt;
		if (amt>0)
			faceRight=true;
		if (amt<0)
			faceRight=false;
	}
	public void setDy(double amt){
		dy=amt;
		jumping=true;
	}
	public void move(){
		box.x=(int)(dx+box.x);
		box.y=(int)(box.y-dy);
		//make sure it not off the edges:
		if (box.y < 0) box.y=0;
		if (box.x<0) box.x=0;
		if (box.x+WIDTH>RIGHT) box.x=RIGHT-WIDTH;

		if (jumping) {
			if(box.y>=bottom){
				dy=0;
				box.y=bottom;
				jumping=false;
			}else{
				dy-=0.1;
			}

		}
	}
	public int getX(){return box.x;}
	public int getY(){return box.y;}
	public int getLife(){return life;}
	public boolean isJumping(){ return jumping&& life>0;}
	public boolean intersects(Rectangle r){return box.intersects(r);}
	public boolean isBelow(Rectangle r){
		return box.y > r.y; //head above top of cloud
	}
}

Stage2.java

import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.imageio.*;


public class Stage2 extends Applet implements KeyListener
{
	public static int BOTTOM=580, TOP=0, LEFT=0, RIGHT=800;

	private BufferedImage sky, chickImg, cloudImg;
	private AudioClip bell,squish;
	private Chick chick;
	private String message;
	private String lastKey;//testing
        private Image screen2;
	private Graphics g2;

	private Rectangle cloudBox;//testing


	public void loadResources(){
		//We load the large files once to save time,
		//getting the resource from the jar file
		try {
					cloudImg = ImageIO.read( getClass().getResource("/Cloud.png"));
					chickImg = ImageIO.read( getClass().getResource("/Chick.png"));
					sky = ImageIO.read( getClass().getResource("/sky.png"));
					bell = getAudioClip(getClass().getResource("/bell.wav"));
					squish = getAudioClip(getClass().getResource("/squish.wav"));

		} catch (Exception e) {
					e.printStackTrace();
		}

	}
	public void init(){
		this.loadResources();
		message="Stage 2: Chick Tester: use keys to move, jump, 'h' to simulate a hit";
		lastKey="";//testing
		cloudBox = new Rectangle (400, 400, 150, (int)(.6*150)); //testing
		chick = new Chick(chickImg, (RIGHT-LEFT-60)/2, BOTTOM);
		addKeyListener(this);
		this.setFocusable(true);
		this.requestFocus();
		screen2 = createImage(RIGHT,BOTTOM+20);
		g2 = screen2.getGraphics();

	}
	public void update(Graphics g){
        paint(g); //get rid of flicker with this method
    }

	public void paint(Graphics g){
		g2.drawImage(sky, LEFT, TOP, null);
		chick.draw(g2);
		g2.drawImage(cloudImg, cloudBox.x, cloudBox.y, 
				      cloudBox.x+cloudBox.width, cloudBox.y+cloudBox.height, 
				      0, 0, cloudImg.getWidth(), cloudImg.getHeight(), this); //testing
		g2.drawString(message, 20, 20);
		g2.drawString("Last Key Typed: "+lastKey, 620, 20);//testing
		g2.drawString("Chick at ("+chick.getX()+", "+chick.getY()+")", 620, 35);//testing
		g2.drawString("Chick Jumping: "+chick.isJumping(), 620, 50);//testing
		g2.drawString("Intersects cloud: "+chick.intersects(cloudBox), 620, 65);//testing
		g2.drawString("below cloud: "+chick.isBelow(cloudBox), 620, 80);//testing
		g2.drawString("life: "+chick.getLife(), 620, 95);//testing

		g.drawImage(screen2, 0,0,this);

		g.dispose();
		repaint();
	}

	@Override
	public void keyTyped(KeyEvent e) {}
	@Override
	public void keyPressed(KeyEvent e) {
		int code = e.getKeyCode();
		if (code == KeyEvent.VK_LEFT || 
			code== KeyEvent.VK_A){
				chick.setDx(-1);
				lastKey="LEFT ("+code+")";//testing
		} 
		if (code == KeyEvent.VK_RIGHT || 
			code == KeyEvent.VK_S){
				chick.setDx(1);
				lastKey="RIGHT ("+code+")";//testing
		}
		if (code == KeyEvent.VK_SPACE || 
			code == KeyEvent.VK_W || 
			code == KeyEvent.VK_UP){
				//jump
				chick.setDy(3);
				lastKey="UP ("+code+")";//testing
				bell.play();
		}
		//testing:
		if (code == KeyEvent.VK_H){
			chick.hit(10);
			lastKey="HIT ("+code+")";//testing
			squish.play();
		}
	}
	@Override
	public void keyReleased(KeyEvent e) {
		int code = e.getKeyCode();
		if (code == KeyEvent.VK_LEFT || 
			code== KeyEvent.VK_A ||
			code == KeyEvent.VK_RIGHT || 
			code == KeyEvent.VK_S	) 
				chick.setDx(0);
	}
}

Stage 3: Making Enemies

Cloud.java

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;


public class Cloud 
{
	private Rectangle box;
	private BufferedImage img;
	private int dir;
	private boolean fallen, dead;
	private double speed, gravity;
	private boolean rising;
	private Font font;

	public Cloud(BufferedImage img, int x, int altitude, int size){
		this.img = img;
		this.dir = 1; // go right
		if(x>0) dir = -1; // go left
		int startX = x-dir*size; //start off screen
		this.box = new Rectangle(startX, altitude, size, (int)(.6*size));
		fallen=false;
		rising=false;
		speed = 1.0+(0.2)*Math.random();
		gravity = .4;
		font = new Font("impact", Font.BOLD, 36);
	}

	public void move(int right, int bottom) {
		if(!fallen && !rising){
			box.x+=(int)(dir*speed);
			fallen = Math.random()<.001; //chance of falling
		}else{

			if (rising){
				box.y-=1;//once hit the cloud rises
			} else {
				box.y+=(int)gravity;
				gravity*=1.005;// 1/2% increase as it falls
			}
		}

		if(dir>0 && box.x > right) dead=true;
		if(dir<0 && box.x+box.width < 0) dead=true;
		if(fallen && box.y+box.height > bottom) {
			dead=true;
		}

		if(rising && box.y+box.height < 0) dead=true;

	}

	public void draw(Graphics g) {
		if(dir>0){
			g.drawImage(img, box.x, box.y, box.x+box.width, box.y+box.height, 0, 0, img.getWidth(), img.getHeight(), null);
		}else{
			g.drawImage(img, box.x, box.y, box.x+box.width, box.y+box.height, img.getWidth(), 0, 0, img.getHeight(), null);
		}
		if(rising){
			g.setColor(Color.RED);
			g.setFont(font);
			g.drawString("100", box.x+box.width/2-25, box.y-20);
		}

	}
	public void hit(){ rising=true;}
	public void setDead(){dead=true;}
	public void setFall() { fallen=true;}
	public boolean isDead() { return dead;}
	public boolean hasFallen() {return fallen;}
	public boolean isRising(){return rising;}
	public Rectangle getBox(){ return box;}
}

Stage3.java

import java.applet.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.*;
import java.io.*;
import java.util.ArrayList;

import javax.imageio.ImageIO;


public class Stage3 extends Applet implements KeyListener
{
	public static int BOTTOM=580, TOP=0, LEFT=0, RIGHT=800;
	private static final double SPEED = .5;//probability of spawning a cloud (testing)
	private BufferedImage sky, chickImg, cloudImg;
	private AudioClip bell,squish;
	private Chick chick;
	private String message;
	private String lastKey;//testing
        private Image screen2;
	private Graphics g2;
	private Font font;
	private ArrayList<Cloud> clouds;


	public void loadResources(){
		//We load the large files once to save time,
		//getting the resource from the jar file
		try {
					cloudImg = ImageIO.read( getClass().getResource("/Cloud.png"));
					chickImg = ImageIO.read( getClass().getResource("/Chick.png"));
					sky = ImageIO.read( getClass().getResource("/sky.png"));
					bell = getAudioClip(getClass().getResource("/bell.wav"));
					squish = getAudioClip(getClass().getResource("/squish.wav"));

		} catch (Exception e) {
					e.printStackTrace();
		}

	}
	public void init(){
		this.loadResources();
		message="Stage 3: Cloud Tester: use 'h' to simulate a hit, 'd' to set dead, 'f' to make it fall";
		lastKey="";//testing
		chick = new Chick(chickImg, (RIGHT-LEFT-60)/2, BOTTOM);
		addKeyListener(this);
		this.setFocusable(true);
		this.requestFocus();
		screen2 = createImage(RIGHT,BOTTOM+20);
		g2 = screen2.getGraphics();
		clouds = new ArrayList<Cloud>();
		font = new Font("helvetica", Font.BOLD, 12);
	}
	public void update(Graphics g)
    {
        paint(g); //get rid of flicker with this method
    }
	public void paint(Graphics g){
		g2.drawImage(sky, LEFT, TOP, null);
		chick.draw(g2);
		updateClouds();
		for (Cloud c:clouds)
			c.draw(g2);
		g2.setFont(font);
		g2.setColor(Color.BLACK);
		g2.drawString(message, 20, 20);
		g2.drawString("Last Key Typed: "+lastKey, 620, 20);//testing
		g2.drawString("Chick at ("+chick.getX()+", "+chick.getY()+")", 620, 35);//testing
		g2.drawString("Chick Jumping: "+chick.isJumping(), 620, 50);//testing
		g2.drawString(clouds.size()+" clouds", 620, 65);//testing
		//testing:
		if (clouds.size()>0){
			Cloud c=clouds.get(0);
			g2.drawString("Cloud at ("+c.getBox().x+", "+c.getBox().y+")", 620, 80);
			g2.drawString("Cloud has fallen: "+c.hasFallen(), 620, 95);
			g2.drawString("Cloud is rising: "+c.isRising(), 620, 110);
			g2.drawString("Cloud id dead: "+c.isDead(), 620, 125);
		}

		g.drawImage(screen2, 0,0,this);

		g.dispose();
		repaint();
	}

	@Override
	public void keyTyped(KeyEvent e) {}
	@Override
	public void keyPressed(KeyEvent e) {
		int code = e.getKeyCode();
		if (code == KeyEvent.VK_LEFT || code== KeyEvent.VK_A){
			chick.setDx(-1);
			lastKey="LEFT ("+code+")";//testing
		} 
		if (code == KeyEvent.VK_RIGHT || code == KeyEvent.VK_S){
			chick.setDx(1);
			lastKey="RIGHT ("+code+")";//testing
		}
		if (code == KeyEvent.VK_SPACE || code == KeyEvent.VK_W || code == KeyEvent.VK_UP){
			//jump
			chick.setDy(3);
			lastKey="UP ("+code+")";//testing
		}
		//testing:
		if (code == KeyEvent.VK_H){
			if(clouds.size()>0)
				clouds.get(0).hit();
			lastKey="HIT ("+code+")";//testing
		}
		//testing:
		if (code == KeyEvent.VK_D){
			if(clouds.size()>0)
				clouds.get(0).setDead();
			lastKey="Set DEAD ("+code+")";//testing
		}
		//testing:
				if (code == KeyEvent.VK_F){
					if(clouds.size()>0)
						clouds.get(0).setFall();
					lastKey="Set FALL ("+code+")";//testing
				}
	}
	@Override
	public void keyReleased(KeyEvent e) {
		int code = e.getKeyCode();
		if (code == KeyEvent.VK_LEFT || code== KeyEvent.VK_A ||
				code == KeyEvent.VK_RIGHT || code == KeyEvent.VK_S	) chick.setDx(0);
	}
	public void spawn(){
		int x = 0;
		if (Math.random()>.5) x=RIGHT;//start of right half the time
		int altitude = randInt(100, 180);
		int size = randInt(60, 150);
		clouds.add(new Cloud(cloudImg, x, altitude, size));
	}
	public int randInt(int min, int max){
		return min+(int)(Math.random()*(max-min+1));
	}

	public void updateClouds(){
		for (Cloud c:clouds){
			c.move(RIGHT, BOTTOM);   
		}
		//Check for dead clouds
		int i=0;
		while(i< clouds.size()){
			Cloud c = clouds.get(i);
			if (c.isDead()) {
				clouds.remove(i);
			}
			else i++;
		}
		if (clouds.size()<1 && Math.random()<SPEED){
			spawn();
		}
	}
}

Stage 4: Collision Detection

CloudController.java

import java.applet.AudioClip;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.util.ArrayList;


public class CloudControler 
{

	private static final int MAX = 10;// number of clouds
	private static final double SPEED = .5;//probability of spawning a cloud
	private BufferedImage img;
	private AudioClip scoreSound;
	private AudioClip damageSound;
	private ArrayList<Cloud> clouds;
	private int right, bottom, score;
	private Chick target;

	public CloudControler(BufferedImage img, AudioClip scoreSound, AudioClip damageSound, 
			int right, int bottom, Chick target) {
		this.img = img;
		this.scoreSound = scoreSound;
		this.damageSound = damageSound;
		clouds = new ArrayList<Cloud>();
		this.right=right;
		this.bottom=bottom;
		this.target = target;
		score = 0;
	}
	public void spawn(){
		int x = 0;
		if (Math.random()>.5) x=right;//start of right half the time
		int altitude = randInt(0, 130);
		int size = randInt(60, 150);
		clouds.add(new Cloud(img, x, altitude, size));
	}
	public int randInt(int min, int max){
		return min+(int)(Math.random()*(max-min+1));
	}
	public void draw(Graphics g){
		updateClouds();
		for (Cloud c:clouds)
			c.draw(g);

	}
	public void updateClouds(){
		for (Cloud c:clouds){
			c.move(right, bottom);
			Rectangle cBox = c.getBox();
		    if(target.intersects(cBox) && target.getLife()>0 && !c.isRising()){
			    	if(target.isJumping() && target.isBelow(cBox) ){
			    		score+=100;
			    		c.hit();
			    		this.scoreSound.play();
			    	}else{	
			    		target.hit(10);
			    		c.setDead();
			    		this.damageSound.play();
			    	}
		    }	    
		}
		//Check for dead clouds
		int i=0;
		while(i< clouds.size()){
			Cloud c = clouds.get(i);
			if (c.isDead()) {
				if (!c.isRising() && target.getLife()>0) score+=10;
				clouds.remove(i);
			}
			else i++;
		}
		if (clouds.size()<MAX && Math.random()<SPEED){
			spawn();
		}
	}
	public String getScore(){ return String.format("%,10d", score) ;}
}

ChickLittleApplet.java

import java.applet.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.*;
import java.io.*;
import javax.imageio.ImageIO;


public class ChickenLittleApplet extends Applet implements KeyListener
{
	public static int BOTTOM=580, TOP=0, LEFT=0, RIGHT=800;

	private BufferedImage sky, chickImg, cloudImg;
	private AudioClip bell,squish;
	private Chick chick;
	private String message;
	private Image screen2;
	private Graphics g2;

	private CloudControler weather;

	private Font font;

	private Font bigFont;

	public void init(){
		this.loadResources();
		message="Arrow keys to move (or a,s), space to jump (or w)";
		chick = new Chick(chickImg, (RIGHT-LEFT-60)/2, BOTTOM);
		weather = new CloudControler(cloudImg, bell, squish, RIGHT, BOTTOM, chick);
		addKeyListener(this);
		this.setFocusable(true);
		this.requestFocus();
		screen2 = createImage(RIGHT,BOTTOM+20);
		g2 = screen2.getGraphics();
		font = new Font("helvetica", Font.BOLD, 12);
		bigFont = new Font("Impact", Font.BOLD, 24);

	}
        public void paint(Graphics g){
		g2.drawImage(sky, LEFT, TOP, null);
		chick.draw(g2);
		weather.draw(g2);
		drawInfo(g2);
		g.drawImage(screen2, 0,0,this);
		g.dispose();
		this.requestFocus();
		repaint();
	}
	public void drawInfo(Graphics g){		
		int life = chick.getLife();
		g.setColor(Color.GREEN);
		if(life <= 30) g.setColor(Color.RED);
		g.fillRect(410, 5, life, 20);
		g.setColor(Color.BLUE);
		g.setFont(font);
		g.drawString(message, 20, 20);
		g.drawRect(410, 5, 100, 20);	
		g.setColor(Color.BLACK);
		g.setFont(bigFont);
		g.drawString("Score: "+weather.getScore(), 570, 25);
		g.drawString("Life: "+life, 325, 25);

	}
	public void loadResources(){
		//We load the large files once to save time,
		//getting the resource from the jar file
		try {
					cloudImg = ImageIO.read( getClass().getResource("/Cloud.png"));
					chickImg = ImageIO.read( getClass().getResource("/Chick.png"));
					sky = ImageIO.read( getClass().getResource("/sky.png"));
					bell = getAudioClip(getClass().getResource("/bell.wav"));
					squish = getAudioClip(getClass().getResource("/squish.wav"));

		} catch (Exception e) {
					e.printStackTrace();
		}

	}
	public void update(Graphics g)
        {
                paint(g); //get rid of flicker with this method
        }

	@Override
	public void keyTyped(KeyEvent e) {}
	@Override
	public void keyPressed(KeyEvent e) {
		int code = e.getKeyCode();
		if (code == KeyEvent.VK_LEFT || code== KeyEvent.VK_A){
			chick.setDx(-1);
		} 
		if (code == KeyEvent.VK_RIGHT || code == KeyEvent.VK_S){
			chick.setDx(1);
		}
		if (code == KeyEvent.VK_SPACE || code == KeyEvent.VK_W || code == KeyEvent.VK_UP){
			//jump
			chick.setDy(3);
		}
	}
	@Override
	public void keyReleased(KeyEvent e) {
		int code = e.getKeyCode();
		if (code == KeyEvent.VK_LEFT || code== KeyEvent.VK_A ||
				code == KeyEvent.VK_RIGHT || code == KeyEvent.VK_S	) chick.setDx(0);
	}
}

Stage 5: Making the jar and the html files

ChickenLittle.html

<html>

    <head>
        <title>Chicken Little Applet</title>
    </head>
    <body>
        <h1>Chicken Little Applet</h1>
        <h2>by Chris Thiel, OFMCap</h2>
        <hr>
        <applet code="ChickenLittleApplet.class" 
            width=800 
            height=600

            archive="ChickenLittle.jar"
            alt="Your browser understands the &lt;APPLET&gt; tag but isn't running the applet, for some reason."
         >

            Your browser is ignoring the &lt;APPLET&gt; tag!      
        </applet>
        <hr><P>
        If the keys aren't working, try clicking on the game (to get the focus back, if you had to click on "Is it ok to run this java applet" or some other security message from your web browser)
        <P>
        Source Code: <a href="http://apcs.mathorama.com/index.php?n=Main.SimpleGameTutorial">apcs.mathorama.com</a><br>
        (free for educational use)
    </body>
</html>

See a working version of it here

If you have a gmail account, you can use google sites and embed your applet. I found some tutorials:

  1. using a jar file at code.google.com
  2. putting a class files in a directory