Java:Tutorials:2 Player Game

From GPWiki

Files:GUITutorial_warn.gif The Game Programming Wiki has moved! Files:GUITutorial_warn.gif

The wiki is now hosted by GameDev.NET at wiki.gamedev.net. All gpwiki.org content has been moved to the new server.

However, the GPWiki forums are still active! Come say hello.

Hello people. This is my first tutorial about game development. I hope that you'll find it useful.

The idea and look

This isn't a complete game. It is rather a demo. 2 players confront eachother. Each controls 3 units which can shoot, cloak and move. The goal is to kill all enemy units.

The code

The game is written in Java. As you might already know, Java is an object-oriented language. But, this game isn't fully written in an OO (object oriented) manner, because the code would be too big to be efficient. I won't post the entire tutorial now - I'll modify it when I have time. For now it's just code with some comments

To create the actual file, copy each of the sections of code into a file called 'ShrafRaf.java'. Note that all of the classes defined here are inner classes of the class ShrafRaf.

import java.io.*;
import java.awt.image.*;
import java.awt.event.*;
import java.awt.*;
import javax.swing.*;
import java.net.*; 

Well, what can I say... just imports to add required classes. The game code is all in one file (and yes, I know that it isn't good, but for practice, you could change that ;D). The main class is called ShrafRaf, but you could call it anyway you want. Just make sure that you save it in apropriate file.

public class ShrafRaf extends JFrame {
	BufferedImage buffer;
	Graphics2D bufferg;
	Unit[] objects;
	Bullet[] allBullets;
	final int numberOfObjects = 10;
	static int brojTemp = 0, gameState = 0, focus = -1, min, numberOfBullets = 0, currentBullet = -1, width, height;
	static Unit u, mu, un;
	static Image background;
	static String adress;
	Communication comm;
	Animation anim;

These variables are used as global variables, because they are needed everywhere in the code.

class Sprite {
	Image picture;
	int x, y, width, height;
	boolean visible;
		
	public Sprite(int X,int Y) {
		x = X;
		y = Y;
		visible = true;
	}
 
 	void move() { 
	}
 
}

This is the super class of all visible elements on the screen. It holds properties and methods which are common for all visible elements (usually called sprites, so class name is sprite).

class Unit extends Sprite {
	float speed, sin, cos, fx, fy;
	boolean owner, clicked, cloaked;
	int target, path, bulletCooldown, life, cloakCooldown, timeCloaked;
	
	public Unit(int x, int y, int s, boolean b) {
		super(x,y);
		speed = s;
		fx = x;
		fy = y;
		owner = b;
		clicked = false;
		bulletCooldown = 0;
		life = 3;
		cloakCooldown = 0;
		timeCloaked = 0;
	}
 
	void move() {
		if(life > 0) {
			int px, py;
			fx += sin;
			fy += cos;
			x = Math.round(fx);
			y = Math.round(fy);
			path -= (int)Math.sqrt(sin * sin + cos * cos);
			if (path <= 0) {
				sin = 0;
				cos = 0;
			}
			bulletCooldown--;
			if (bulletCooldown < 0) {
				bulletCooldown = 0;
			}
			cloakCooldown--;
			if(cloakCooldown < 0) {
				cloakCooldown = 0;
			}
			timeCloaked--;
			if(timeCloaked < 0){
				cloaked = false;
				timeCloaked = 0;
			}
		}
		else {
			visible = false;
		}
	}
 
	void shoot(int x, int y) {
		if(bulletCooldown == 0) {
			if(numberOfBullets < 9) {
				numberOfBullets++;
				if (currentBullet == 9) {
 					currentBullet = 0;
				}
 				else {
 					currentBullet++;
 				}
 				allBullets[currentBullet] = new Bullet(this.x, this.y, x, y, true);
 				bulletCooldown = 50;
 				try {
					comm.send(6+focus,x,y);
 				}	
 				catch(Exception e) { // <-- Bad!!
 					//This should have code that handles the Exception, eg:
					//e.printStackTrace();
 				}
			}
 		}
	}
}

As you can see, this class is a subclass of Sprite. It inherits everything from it, but it adds its own functionality to the Sprite object.

class Bullet extends Sprite {
	float sin, cos, fx, fy;
	int path;
	boolean owner;
 
	public Bullet(int cx, int cy, int dx, int dy, boolean vlas) {
		super(cx, cy);
		int hx, hy;
		fx = cx;
		fy = cy;
		hx = dx - cx;
      		hy = dy - cy;
		float h = hyp(hx, hy);
		sin = hx / h * 12;
     		cos = hy / h * 12;
     		path = width + 500;
     		owner = vlas;
	}
		
	void drawEverything() {
		bufferg.fillOval(x-3,y-3,6,6);
	}
 
	void move() {
		fx += sin;
		fy += cos;
		x = Math.round(fx);
		y = Math.round(fy);
		path -= (int)Math.sqrt(sin*sin + cos*cos);
		if (path <= 0){
			sin = cos = 0;	
			visible = false;	
		}		
	}
}

Bullet - also a Sprite - which can be fired.

class Animation extends Thread {		
	public void run() {
		while (true) {
			drawEverything();
			try{
				sleep(20);
			}
			catch(Exception e) {
				e.printStackTrace();
			}
		}
	}
}

Animation is the main loop of the program. It does only one important thing-draws everything :D

class Communication extends Thread {
	DataInputStream input;
	DataOutputStream">DataOutputStream output;
	ServerSocket server;
	Socket socket;
	final int port = 1234;
	int x, y, sprite, i = 0;
		
	public Communication(int tip) {
		if (tip == 1) {
			try {
				server = new ServerSocket(port);
				socket = server.accept();
				System.out.println("server started");	
				input = new DataInputStream(socket.getInputStream());
				output = new DataOutputStream">DataOutputStream(socket.getOutputStream());
				min = 0;
				addSprite("pics/paw.gif", true, 6, 50, height / 4, 50, 50);
				addSprite("pics/heart.gif", true, 6, 50, height / 2, 50, 50);
				addSprite("pics/star.gif", true, 6, 50, (height / 4) * 3, 50, 50);
				addSprite("pics/rpaw.gif", false, 6, width - 50, height / 4, 50, 50);
				addSprite("pics/rheart.gif", false, 6, width - 50, height / 2, 50, 50);
				addSprite("pics/rstar.gif",false,6,width-50,height/4*3,50,50);	
			}	
			catch(Exception e) { // <-- Bad!!
				//System.err.println("Greska"); // Perhaps this mean "error"?
				e.printStackTrace();
			}
		}
		else {
			try {
				socket = new Socket(adress,port);
				System.out.println("client started");	
				input = new DataInputStream(socket.getInputStream());
				output = new DataOutputStream">DataOutputStream(socket.getOutputStream());
				min = 3;
				addSprite("pics/paw.gif", false, 6, 50, height / 4, 50, 50);
				addSprite("pics/heart.gif", false, 6, 50, height / 2, 50, 50);
				addSprite("pics/star.gif", false, 6, 50, (height / 4) * 3, 50, 50);
				addSprite("pics/rpaw.gif", true, 6, width - 50, height / 4, 50, 50);
				addSprite("pics/rheart.gif", true, 6, width - 50, height / 2, 50, 50);
				addSprite("pics/rstar.gif", true, 6, width - 50, (height / 4) * 3, 50, 50);	
			}
			catch(Exception e) { //<-- Bad!!
				//System.err.println("Greska");
				e.printStackTrace();
			}
		}
		focus = min;
		objects[min].clicked = true;
	}
 
	void send(int s, int x, int y) throws Exception {
		output.writeInt(s);
		output.writeInt(x);
		output.writeInt(y);
	}
 
	public void run() {
		while (true) {
			try {
			sprite = input.readInt();
				x = input.readInt();
				y = input.readInt();
				receive(sprite,x,y);
			}
			catch(Exception e){
				e.printStackTrace();
			}
		}
	}
 
	void receive(int sprite, int x, int y) {
		if (sprite > 5) {
			un = objects[sprite - 6];
			if (numberOfBullets < 9) {
				numberOfBullets++;
			}
 			if (currentBullet == 9) {
 				currentBullet = 0;
			}
 			else {
 				currentBullet++;
			}
 			allBullets[currentBullet] = new Bullet(un.x, un.y, x, y, false);
 			un.bulletCooldown = 50;
		}
		else if (sprite < 0) {
			sprite = -sprite - 1; // (Edit: not sure about this line)
			objects[sprite].cloaked = true;
			objects[sprite].timeCloaked = 100;
			objects[sprite].cloakCooldown = 100;
		}
		else {
			int dx, dy;
		//	System.out.println("primljeno" + sprite + " " + x + " " + y);
			un = objects[sprite];
			dx = x - un.x;
    		  	dy = y - un.y;
			float h = hyp(dx,dy);
			un.sin = dx / h * un.speed;
	    	 	un.cos = dy / h * un.speed;
    		 	un.path = (int)h;
		}	
	}
}

Class which communicates with the remote computer. It can be created as a client or a server.

void drawEverything() {
	switch(gameState) {
	case 2:{
		bufferg.setColor(Color.YELLOW);
		bufferg.fillRect(0, 0, width - 1, height - 1);
		bufferg.setColor(Color.BLACK);
		for (int i = 0; i < brojTemp; i++) {
			un = objects[i];
			if(un.visible) {
				un.move();
				if(un.cloaked) {
					if(un.owner == true) {
						bufferg.drawImage(un.picture, un.x - 25, un.y - 25, this);
						bufferg.setColor(Color.blue);
						bufferg.drawOval(un.x - 24, un.y - 24, 48, 48);
					}
				}						
				else {
					bufferg.drawImage(un.picture, un.x - 25, un.y - 25, this);
				}
			}
		}
		bufferg.setColor(Color.BLACK);
		if(numberOfBullets > 0) {
			for (int j = 0; j < numberOfBullets; j++) {
				if(allBullets[j].visible){
					allBullets[j].move();
					allBullets[j].drawEverything();
					for(int k = 0; k < 6; k++) {
						if(objects[k].visible) {
							float hipo = hyp(allBullets[j].x - objects[k].x, allBullets[j].y - objects[k].y);
							if (hipo <= 20 && allBullets[j].owner != objects[k].owner) {
								objects[k].life--;
								allBullets[j].visible = false;
								break;
							}
						}	
					}
				}
			}
		}
		if(objects[focus].visible) {
			bufferg.drawOval(objects[focus].x - 25, objects[focus].y - 25, 50, 50);
			bufferg.drawString("life:" + objects[focus].life, 15, 15);
		}
	//	bufferg.fillPolygon(nizX,nizY,6);
		repaint();
		break;
	}
	case 0:{
		bufferg.drawImage(background, 0, 0, this);
		repaint();
		break;
	}
	case 1:{
		bufferg.setColor(Color.WHITE);
		bufferg.fillRect(0, 0, width - 1, height - 1);
		bufferg.setColor(Color.BLACK);
		bufferg.drawString("Kliknite za pocetak igre", 500, 300);
		repaint();
		break;
	}	
}

Famous drawEverything method. It draws every sprite and background on the screen!

void addSprite(String s, boolean owner, int sp, int x, int y, int width, int height) {
	objects[brojTemp] = new Unit(x, y, sp, owner);	
	objects[brojTemp].picture = getToolkit().getImage(s);
	objects[brojTemp].width = objects[brojTemp].x + width;
	objects[brojTemp].height = objects[brojTemp].y + height;
	brojTemp++;
}

Method which adds a new Sprite (in this case unit).

public ShrafRaf() {
 
}
 
public ShrafRaf(String s){
	super(s);
}

Constructors for the JFrame.

public void frameInit() {
	super.frameInit();
	buffer = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
	bufferg = buffer.createGraphics();
	bufferg.setStroke(new BasicStroke(3f));
	objects = new Unit[numberOfObjects];
	allBullets = new Bullet[numberOfObjects];
	background = getToolkit().getImage("pics/background.jpg");
	anim = new Animation();
	anim.start();
	setDefaultCloseOperation(EXIT_ON_CLOSE);
 
	//A KeyAdapter could also be used here
	KeyListener keybListener = new KeyListener() {
		public void keyPressed(KeyEvent e) {
			if(e.getKeyChar() == 'a') {
				focus = min;
				objects[focus].clicked = true;
			}
			if(e.getKeyChar()=='s') {
				focus = min + 1;
				objects[focus].clicked = true;
			}
			if(e.getKeyChar() == 'd') {
				focus = min + 2;
				objects[focus].clicked = true;
			}
			if(e.getKeyCode() == e.VK_SPACE) {
				if(focus > -1 && objects[focus].cloakCooldown == 0) {
					objects[focus].cloaked = true;
					objects[focus].timeCloaked = 170;
					objects[focus].cloakCooldown = 400;
					try {
						comm.send(-(focus+1),0,0);
					}
					catch(Exception ex){
						ex.printStackTrace();
					} // <-- Bad!!
				}
			}
		}
 
		public void keyReleased(KeyEvent e) {
			//Do nothing
		}
		public void keyTyped(KeyEvent e) {
			//Do nothing
		}
	};
	
	//A MouseAdapter could also be used here	
	MouseListener mouseListener = new MouseListener() {
		public void mouseClicked(MouseEvent e) {
			//Do nothing
		}
	     	public void mouseEntered(MouseEvent e) {
			//Do nothing
		}
      		public void mouseExited(MouseEvent e) {
			//Do nothing
		}
      		public void mousePressed(MouseEvent e) {
      			int ex = e.getX(), ey = e.getY(), X = 0, Y = 0, dx, dy, i;
  			float hipo;
      			switch(gameState) {
      				case 2: {
      					loop:
      					for (i = 0; i < brojTemp; i++) {
      						u = objects[i];
      						if (u.visible) {
      							dx = ex - u.x;
      							dy = ey - u.y;
      							hipo = hyp(dx, dy);
      							if (hipo <= 25 && u.owner == true) {
      								if (focus > -1 && focus != i) {
      									objects[focus].clicked = false;
      								}
      								focus = i;
      								break loop;
      							}
      						}
      					}
      					if(focus > -1 && objects[focus].clicked) {
      						mu = (Unit)objects[focus];
      						if (e.getButton() == e.BUTTON3) {
      							mu.shoot(ex, ey);	
      						}
      						else {
      							dx = ex - mu.x;
      							dy = ey - mu.y;
      							try {
      								comm.send(focus, ex, ey);
      							}
      							catch(Exception exc){
								exc.printStackTrace();
							}
      							hipo = hyp(dx, dy);
      							mu.path = (int)hipo;
      							mu.sin = dx / hipo * mu.speed;
      							mu.cos = dy / hipo * mu.speed;
      						}
      					}
      					if (focus > -1) {
      						objects[focus].clicked = true;
					}
      					break;
      				}
      				case 0: {
      					if(ex > 300 && ex < 700 && ey > 110 && ey < 200) {
      						comm = new Communication(1);
      						comm.start();
      						gameState = 1;
      					}
      					if(ex > 300 && ex < 700 && ey > 325 && ey < 430) {
      						comm = new Communication(0);
      						comm.start();
      						gameState = 1;
      					}
      					break;
      				}
      				case 1: {
      					gameState = 2;
      				}
      			}
        	}
      		public void mouseReleased(MouseEvent e) {
          	}
	};
	addMouseListener(mouseListener);
	addKeyListener(keybListener);
}

This method is called upon initialization of the window. Also it handles mouse events and keyboard events.

public void paint(Graphics g) {
		g.drawImage(buffer, 0, 0, this);
}

This method draws the buffer in the JFrame.

float hyp(int x,int y) {
	return (float)Math.sqrt(x*x + y*y);
}
 
public static void main(String args[]) {
	BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
	//System.out.println("Unesite sirinu(prvu komponentu u rezoluciji ekrana)!");
	try {
		width = Integer.parseInt(input.readLine());
		//System.out.println("Unesite visinu(druga komponenta u rezoluciji ekrana)!");
		height = Integer.parseInt(input.readLine());
		//System.out.println("Unesite ip adresu na koju cete se konektovati!");
		adress = input.readLine();
		input.close();
	}
	catch(Exception e){
		e.printStackTrace();
	}
	JFrame f = new ShrafRaf();
	f.setSize(width, height);
	f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	f.setCursor(Cursor.CROSSHAIR_CURSOR);
	f.setUndecorated(true);
	f.show();		
}
 
 
} //This is the end of the ShrafRaf class. 

Get user input and start the game!!!

Also, I will modify the code so it will be more "tutorial friendly". That's it for now.

TODO

Tips for readers... coming soon...