Rodrixar

Monday, October 11

Java Applet Tutorial

How to create a Java Online Game

Overview


In this simple introductory Java Applet tutorial, we’ll build a straightforward Super Mario Brother online. We’ll actually just animate Luigi running around the screen and responding to user input. The controls will be very basic: move Luigi using the left and right arrow keys (← to move him to the left, and → to move him to the right), and change the speed at which he moves by pressing the up and down keys. The controls are shown on the screen and are indicated by the "thread break" marker.

For a quick demo of the final product, checkout the box below:



Also, be sure to grab some sprite images for your game:

Super Luigi Brother Online Frame 0,1Super Luigi Brother Online Frame 0,2Super Luigi Brother Online Frame 0,3Super Luigi Brother Online Frame 0,4Super Luigi Brother Online
Super Luigi Brother Online Frame 1,0Super Luigi Brother Online Frame 1,1Super Luigi Brother Online Frame 1,2Super Luigi Brother Online Frame 1,3Super Luigi Brother Online Frame 1,4


The Set Up: Intro to Java Applets

Probably the first thing you’ll notice about Java Applets is the fact that they don’t have a main() method like all other Java programs do. Instead, Java Applets will have an init() method, which must be a public, void returning method (similar to the standard main).
Also, Java applets are meant to be viewed through a browser, so be sure to create an HTML file to run your app in. I will show you how to do that when we get to that point.
Finally, I will show you the libraries we’ll be working with, and therefore will need to import.

The Java.Applet Library

In order to create our demo applet, go ahead and import the following libraries to your Luigi.java file. This will be the file we’ll build for this tutorial.

import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.net.*;


Those libraries take care of making our applet, loading files from a web server, and listening and responding to user input events.

Sprite Animation in Java

In order to simplify this tutorial, and yet make this code somewhat more portable and expandable, let’s create a class that will handle the individual sprite animations that we’ll use. Actually, let’s create a class to represent Luigi, who will be our hero in this case. This class will basically have the following member variables:

private int x; // Luigi’s on screen x-coordinate
private int y; // Luigi’s on screen y-coordinate
private int dtX; // The delta-x; the amount of units to move Luigi horizontally each time he moves
private int pos; // To keep track of when Luigi moves outside the screen
private String name; // Represents the name of our character
private boolean K_RIGHT; // Is the user pressing the right key
private boolean K_LEFT; // Is the user pressing the left key
Image[] src; // An array to keep all the sprite images used in the game


That’s pretty simple stuff. We can initialize those variables right on our constructor. I’ve found it helpful to offer at least a couple of choices to the programmer using your code, so in this tutorial, we’ll create two constructors for this Sprite class: one with parameters to initialize a Sprite object (a character for your game), and one that takes no parameters. This second constructor will actually just call the first-mentioned constructor with some default values. This way we have two methods doing the same thing, but not duplicating hardly any code at all. So here are the constructors:

public Sprite(String pName, int pX, int pY)
{
name = pName;
x = pX;
y = pY;
dtX = 10;
pos = 0;
src = new Image[8];
K_RIGHT = false;
K_LEFT = false;
}

public Sprite()
{
this("Tupac", 10, 10); // The name and starting position of our default character
}


And in order to keep this class simple, yet functional, let’s add a few methods:

public int x() // returns the x position of your character
public int y() // returns the y position of your character
public void walk(String dir) // moves your character horizontally
public void stand(String dir) // changes the character’s sprite image if he’s not moving
public int pos() // returns the x position of the character
public boolean K_LEFT() // tell us if the user is pressing the right key
public boolean K_RIGHT() // tell us if the user is pressing the right key
public void setKey(String dir, boolean state) // respond to the user pressing the right or left key


You will notice that our Sprite class could have used less methods, had only the member states been more accessible. It is good practice, however, to restrict member states as much as reasonably possible, and create good ol’ getters and setters. Yes, the Java likes programmers that use a getter and setter whenever possible.

Sprite Class Code

Most methods should be pretty self-explanatory and easy to follow… For the most part we just check for the current state of our character, increment his x-position by a uniform amount (determined by delta-X), and make sure he is never displayed outside the screen boundaries.

class Sprite
{
private int x;
private int y;
private int dtX;
private int pos;
private String name;
private boolean K_RIGHT;
private boolean K_LEFT;
Image[] src;

public Sprite(String pName, int pX, int pY)
{
name = pName;
x = pX;
y = pY;
dtX = 10;
pos = 0;
src = new Image[8];
K_RIGHT = false;
K_LEFT = false;
}

public Sprite()
{
this("Tupac", 10, 10);
}

public int x()
{
return x;
}

public int y()
{
return y;
}

public void walk(String dir)
{
if (dir == "left")
{
x -= dtX;
if (pos < pos =" 5;"> 7)
pos = 5;

if (x < -50) x = 450; } else if (dir == "right") { x += dtX; if (pos > 3)
pos = 1;
if (pos <> 3)
pos = 1;

if (x > 450)
x = -50;
}
}

public void stand(String dir)
{
if (dir == "left")
{
pos = 4;
}
else if (dir == "right")
{
pos = 0;
}
}

public int pos()
{
return pos;
}

public boolean K_LEFT()
{
return K_LEFT;
}

public boolean K_RIGHT()
{
return K_RIGHT;
}

public void setKey(String dir, boolean state)
{
if (dir == "right")
K_RIGHT = state;
if (dir == "left")
K_LEFT = state;
}
}



Java Applet’s Main()
As mentioned earlier, the method main() is not used in applets like in regular java apps. Here we use the method init(). In our case, we’ll be extending the class Applet (all applets will), and we’ll implement the following classes and interfaces: ActionListener (for general event listening), KeyListener (for specific keyboard events), and Runnable (to create threads!).
Once inside your main game class, just do what you would in any other class, but keep in mind that the program will enter in through init(). In this tutorial, we’ll create the following state variables:

final static int WIDTH = 500; // the width of our screen area
final static int HEIGHT = 200; // the height of our screen area
private int FPS; // not really the framerate, just a way to keep the animation constant
private String TSE; // phrase to display if we get an Interrupted Exception while trying to put our animation thread to sleep for FPS milliseconds
Thread runner; // where we’ll keep Luigi
Image image; // this is how we’ll add a double buffer to our app
Graphics graphics; // this is also part of the double buffer method we’re using.
Sprite hero; // the manifestation of our Sprite class


As far as the methods we’ll use to control our awesome Super Mario Brother Online game, featuring Luigi, we’ll keep it simple. Most methods are defined because we’re implementing interfaces that require us to define them, even if we make them blank. This is a key feature of the Java Language. Be sure to understand Java’s polymorphism, inheritance, interfaces, and abstract classes if this confuses you. In case you don’t know what any of that means, it might sound like a lot of new stuff to learn, but really, it’s just one single concept, which is not really very complicated to understand. But it is very fundamental that you at least have a general idea about those topics if you want to become a decent Java Game Programmer…

public void paint(final Graphics g)
public void actionPerformed(ActionEvent e)
public void keyTyped(KeyEvent key) {}
public void keyReleased(KeyEvent key) {
public void keyPressed(KeyEvent key)
public void start()
public void stop()
public void run()
public void update(Graphics g)


The main meat of the code takes place inside the run method of our runner thread. You can think of this as the Luigi thread. Basically, we check for the current state of the app. If the user is pressing a key, for example, that will be a different state than the app would be in if no keys were being pressed at the time the thread performs this checks. The thread basically checks for those states constantly… If the right or left key is in fact being pressed, he call Luigi’s move method and moving to the right or left based on the app state. We then tell the app to refresh the screen images, and we tell the thread (just Luigi’s thread) to sleep() for a brief amount of time. If we don’t do this crucial step, the thread will run as fast as it can. This would mean the thread would run faster/slower depending on the computer running the app. Adding the call to sleep() makes our app constant across different computers.

In case you don’t know much about threads, what happens is that our app will do various things in different threads, but all at the same time. It would be weird to check first if there are keys being pressed, then if so, tell Luigi to move, then redraw his image on the screen, then make sure he’s not outside the screen, etc… through threads we can check on a key being pressed on the keyboard, while at the same time we can be redrawing the screen, and at the same time we can be performing a calculating to see where Luigi is positioned.

public class Luigi extends Applet implements ActionListener, KeyListener, Runnable
{
/**
*
*/
private static final long serialVersionUID = 1L;

final static int WIDTH = 500;
final static int HEIGHT = 200;
private int FPS;
private String TSE;
Thread runner;
Image image;
Graphics graphics;
Sprite hero;

public void init()
{
image = createImage(WIDTH, HEIGHT);
graphics = image.getGraphics();
FPS = 100;

resize(WIDTH, HEIGHT);
hero = new Sprite("Luigi", 20, 50);

addKeyListener(this);

setLayout(new FlowLayout());
setBackground(Color.white);

/***
* Here we’re loading the images from a sub-directory called "luigi_img" where the images are stored. Since we’re using the java.net library, we can actually load the images straight from a web server (like "http://www.rodrigo-silveira.com/dharma/cs246/img/luigi.png").
*
***/
hero.src[0] = getImage(getDocumentBase(), "luigi_img/luigi,0r.gif");
hero.src[1] = getImage(getDocumentBase(), "luigi_img/luigi,1r.gif");
hero.src[3] = getImage(getDocumentBase(), "luigi_img/luigi,2r.gif");
hero.src[2] = getImage(getDocumentBase(), "luigi_img/luigi,3r.gif");
hero.src[4] = getImage(getDocumentBase(), "luigi_img/luigi,0l.gif");
hero.src[5] = getImage(getDocumentBase(), "luigi_img/luigi,1l.gif");
hero.src[7] = getImage(getDocumentBase(), "luigi_img/luigi,2l.gif");
hero.src[6] = getImage(getDocumentBase(), "luigi_img/luigi,3l.gif");
}

public void paint(final Graphics g)
{
update(g);
}

public void actionPerformed(ActionEvent e)
{
repaint();
}

public void keyTyped(KeyEvent key) {}
public void keyReleased(KeyEvent key) {
if (key.getKeyCode() == KeyEvent.VK_RIGHT)
{
hero.setKey("right", false);
hero.stand("right");
}
else if (key.getKeyCode() == KeyEvent.VK_LEFT)
{
hero.setKey("left", false);
hero.stand("left");
}

else if (key.getKeyCode() == KeyEvent.VK_UP)
{
FPS++;
}
else if (key.getKeyCode() == KeyEvent.VK_DOWN)
{
FPS--;
}
}

public void keyPressed(KeyEvent key)
{
if (key.getKeyCode() == KeyEvent.VK_RIGHT)
{
hero.setKey("left", false);
hero.setKey("right", true);
}
else if (key.getKeyCode() == KeyEvent.VK_LEFT)
{
hero.setKey("right", false);
hero.setKey("left", true);
}
repaint();
}

public void start()
{
if (runner == null)
{
runner = new Thread(this);
runner.start();
}
}

public void stop(){}

public void run()
{
while (runner != null)
{
repaint();

try
{
Thread.sleep(FPS);
if (hero.K_LEFT())
{
hero.walk("left");
}
else if (hero.K_RIGHT())
{
hero.walk("right");
}
}
catch(InterruptedException e)
{
TSE = "Exception caught while trying to put thread to sleep...";
}
}
}

public void update(Graphics g)
{
g.drawImage(image, 0, 0, this);
g.drawImage(hero.src[hero.pos()],hero.x(),hero.y(),this);

g.setColor(Color.red);
g.drawString("Luigi (" + hero.x() + ", " + hero.y() + ")", 10, 10);
g.setColor(Color.black);
g.drawString("Thread break = " + FPS, 10, 25);

// Display errors
if (TSE != null)
g.drawString("Thread Sleep Error: " + TSE, 10, 40);
}
}

Conclusion

While this Java Applet tutorial was almost overly simplistic, the concepts we covered are pretty important. In case you don’t quite understand the way threads work, or how we can implement interfaces in Java, this would be a recommended subject to learn next. If you did follow the code without much confusion, then the challenge would be for you to try to improve on it. You will notice that by pressing the up/down arrow keys you can dynamically change the value of FPS, which will tell the thread to do what it does more times per second. Right now the FPS speed controls both the animation speed (how fast we change the image frames), and how fast Luigi moves. Can you think of a better way to do this? What if we want to move Luigi faster, but keep the animation as smooth? Try implementing different things, but be sure to check back soon for my follow up tutorial, where we’ll be adding more physics to Luigi. We’ll make him slide (like if he is running on ice), and jump. We’ll also add some background imagery to make the level prettier to look at.

2 comments:

  1. pretty good your tutorial, thank you so much.

    ReplyDelete
  2. Online Java tutorials are far more effective learning options today for learners wanting to have knowledge on Java Platform which is a hardware or software environment in which a program runs.

    ReplyDelete