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
Feel free to use your own sounds and art, but here are some I've made using GIMP and Audacity for you to use:
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:
- 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.
- 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 <APPLET> tag but isn't running the applet, for some reason." > Your browser is ignoring the <APPLET> 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: