Breaking News

Game Deveopment Tutorial with Java:part 2


Until now every thing is clear and simple, so we will try another example which is more complicated than the previous ones. Let that we have more than one ball and each ball has direction and speed to the movement. Besides that it has a color.
So I will try to gather this properties of the ball into one class called Ball.
public class Ball {
      int x,y;                             // position
      Color c=Color.black;                 //color
      int xDirection =0 , yDirection = 0 ; // the directions
      int speed = 0 ;                      // speed of the movement
int radius = 10;
}
Each ball will move and will draw itself so we will need to develop the two methods
public void move(){
      x+= xDirection/Math.abs(xDirection) * Math.abs(speed);
      y+= yDirection/Math.abs(yDirection) * Math.abs(speed);
}
     
public void paint(Graphics g) {
      //draw the red ball
      g.setColor(c);
      g.fillOval(x - radiusy - radius, 2 * radius, 2 * radius);
}

Example 3
Here are the final class of the Ball
import java.awt.Color;
import java.awt.Graphics;

public class Ball {
      int xy// position
      Color c = Color.black//color
      int xDirection = 0, yDirection = 0; // the directions
      int speed = 0; // speed of the movement
      int radius = 10;

      public Ball() {
            y = x = 0;
      }

      public Ball(int x1, int y1) {
            x = x1;
            y = y1;
      }

      public Ball(int x1, int y1, int r) {
            x = x1;
            y = y1;
            radius = r;
      }

      public void move() {
            x += xDirection / Math.abs(xDirection) * Math.abs(speed);
            y += yDirection / Math.abs(yDirection) * Math.abs(speed);
      }

      public void paint(Graphics g) {
            //draw the red ball
            g.setColor(c);
            g.fillOval(x - radiusy - radius, 2 * radius, 2 * radius);
      }

      public int getX() {
            return x;
      }

      public void setX(int x) {
            this.x = x;
      }

      public int getY() {
            return y;
      }

      public void setY(int y) {
            this.y = y;
      }

      public Color getC() {
            return c;
      }

      public void setC(Color c) {
            this.c = c;
      }

      public int getXDirection() {
            return xDirection;
      }

      public void setXDirection(int direction) {
            xDirection = direction;
      }

      public int getYDirection() {
            return yDirection;
      }

      public void setYDirection(int direction) {
            yDirection = direction;
      }

      public int getSpeed() {
            return speed;
      }

      public void setSpeed(int speed) {
            this.speed = speed;
      }

}


And we are now to develop the anaimator which will draw the balls.
import java.applet.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.Random;
/**
 *
 * @author muhammad.hamed
 *
 */
public class Animation3 extends Applet implements Runnable {
      ArrayList balls = new ArrayList(); //list of balls

      public void init() {         
            setBackground(Color.white);
      }

      public void start() {
            //add balls with random colors and random directions
            Color[] randColors = new Color[]{Color.red,Color.blue,Color.black,Color.ORANGE,Color.green};
            for(int i = 0 ; i <5 ;i++){
                  Random r = new Random();                 
                Ball ball = newBall(100+r.nextInt(10),100+r.nextInt(10),3+r.nextInt(10));
                ball.setC(randColors[r.nextInt(randColors.length)]);
                ball.setXDirection(r.nextBoolean()?1:-1);
                ball.setYDirection(r.nextBoolean()?1:-1);
                ball.setXDirection(r.nextBoolean()?1:-1);
                ball.setSpeed(r.nextInt(5));
                
                balls.add(ball);
            }          
            Thread th = new Thread(this);
            th.start();
      }

      public void stop() {

      }

      public void destroy() {

      }

      public void run() {
            while (true) {
                  for (int i = 0; i < balls.size(); i++) {
                        Ball ball = (Ball)balls.get(i);
                        ball.move();
                  }
                  repaint();
                  try {
                        Thread.sleep(300);
                  } catch (InterruptedException ex) {
                  }
            }
      }

      public void paint(Graphics g) {
            for (int i = 0; i < balls.size(); i++) {
                  Ball ball = (Ball)balls.get(i);
                  ball.paint(g);
            }
      }

}


Double buffering

I'm sure you recognized in the first applet that the circle is flickering. There is a very simple reason for this. Every time the paint - method is called by repaint() the applet screen is cleared completely. Because of this we can see for a millisecond a absolutely blank screen. To suppress these phenomena, we have three possibilities:
  1. We don't clear the screen at all
  2. We clear the screen only where something is changing
  3. We use double buffering

Don't clear the screen at all

This idea seems to be the solution to all our problems (but it is not!). In our case it would mean that the ball would paint a thick red line across the applet because the screen stays red at every position the ball has been once. This might be ok for some situations but we want to see a moving ball, not a thick red line. So this technique is just usefull for objects that are not moving.
At first it is important for you to know one thing. The call of repaint() doesn't call the paint() - method at the same time. Instead a method called update() is called. If one doesn't overwrite this method, update() clears the complete screen and afterwards calls paint() which paints the background and our circle again. To avoid clearing the screen
 you have to overwrite the update() - method. Our new update() - method doesn't clear the screen anymore, but just calls paint(). This can be done with three line of code:
public void update(Graphics g) {
      paint(g);
}
As I said before this is not solution for our applet, But it is essential to understand that repaint() doesn't call paint() but update() which calls paint() then!!

Clear the screen only where something is changing

This solution is based on the idea to repaint just these parts of our applet where something has changed. This concept is very good for a game like snaking. If the last part of your snake is colored in the same color as the background, this part will over paint the parts of the snake, where the snake has been. I don't want to talk about this solution in detail, because one could use this method just in very special situations. So let's talk about the double buffering which is a really good and effective solution to avoid a flickering screen and the best of it all: You can use this method in every applet the same way as I do now, so you'll never have to worry about that problem again!

Double buffering

As I said you can use double buffering in every applet in a very easy way. Double buffering means to paint all the things in the paint() method to a off-screen image. If all things, that have to be painted, are painted, this image is copied to the applet screen. The method does the following in detail:
  1. Generate a new off-screen-image by using create Image and store this image in a instance variable( = generate a empty image)
  2. Call getGraphics to get graphic context for this image
  3. Draw everything (including to clear the screen complete) on the off-screen image ( = draw image in the background)
  4. When finished, copy this image over the existing image on the screen. ( = draw image in the foreground)
This technique means that the picture is already painted before it is copied to the screen. When coping the image to the foreground the old pixels are overlapped by the new ones. There won't be any flickering picture anymore because there is not a millisecond you could see a empty screen! 
The only disadvantage of the double buffering is, that it produces a large amount of data and every image is drawn two times (off-screen and when coping to the screen). But in most cases and on a fast computer this is much better than wasting time on finding anther solution! 
Well after all this theories I will show you how to integrate the double buffering into our "ball moving" applet:

Double buffering: the code


// declare two instance variables at the head of the program private Image dbImage;
      private Graphics dbg; 

      ... other code ...

      /** Update - Method, implements double buffering */
      public void update (Graphics g)
      {
      // initialize buffer
      if (dbImage == null)
      {
dbImage = createImage (this.getSize().width,this.getSize().height); 
            dbg = dbImage.getGraphics ();
      }

      // clear screen in background
      dbg.setColor (getBackground ());
      dbg.fillRect (0, 0, this.getSize().width, this.getSize().height);

      // draw elements in background
      dbg.setColor (getForeground());
      paint (dbg);

      // draw image on the screen
      g.drawImage (dbImage, 0, 0, this);
}



As I said before you can copy and paste this code into every applet that uses animations!
Here is a fast version of the pervious example with double buffering.
Example 4
import java.applet.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.Random;

/**
 * @author muhammad.hamed
 */
public class Animation4 extends Applet implements Runnable {
      ArrayList balls = new ArrayList();
      private Image dbImage;
      private Graphics dbg;

      public void init() {
            setBackground(Color.white);
      }

      public void start() {
            // add balls with random colors and random directions
            Color[] randColors = new Color[] { Color.red, Color.blue, Color.black,
                        Color.ORANGE, Color.green };
            for (int i = 0; i < 5; i++) {
                  Random r = new Random();
                  Ball ball = new Ball(100 + r.nextInt(10), 100 + r.nextInt(10),
                              3 + r.nextInt(10));
                  ball.setC(randColors[r.nextInt(randColors.length)]);
                  ball.setXDirection(r.nextBoolean() ? 1 : -1);
                  ball.setYDirection(r.nextBoolean() ? 1 : -1);
                  ball.setXDirection(r.nextBoolean() ? 1 : -1);
                  ball.setSpeed(r.nextInt(5));

                  balls.add(ball);
            }
            Thread th = new Thread(this);
            th.start();
      }

      public void stop() {

      }

      public void destroy() {

      }

      public void run() {
            while (true) {
                  for (int i = 0; i < balls.size(); i++) {
                        Ball ball = (Ball) balls.get(i);
                        ball.move();
                  }
                  repaint();
                  try {
                        Thread.sleep(100);
                  } catch (InterruptedException ex) {
                  }
            }
      }

      // the double buffering code
      public void update(Graphics g) {         
            if (dbImage == null) {
                  dbImage = createImage(this.getSize().width,this.getSize().height);
                  dbg = dbImage.getGraphics();
            }
           
            dbg.setColor(getBackground());
            dbg.fillRect(0, 0, this.getSize().width,this.getSize().height);
           
            dbg.setColor(getForeground());
            paint(dbg);
           
            g.drawImage(dbImage, 0, 0, this);
      }

      public void paint(Graphics g) {
            for (int i = 0; i < balls.size(); i++) {
                  Ball ball = (Ball) balls.get(i);
                  ball.paint(g);
            }
      }
}


Using Images


                Until now we do not build a game we just work on the basics of the game programming. But do you think that all the time we will use the draw ovals and rectangles it would be hard to do so, This what I’m going to speak about. If we used an image of a ball rather than the fillOval functions it would be fine. What about using simple image as a background? You can get overview of the images in game in this site http://www.tutorialized.com/view/tutorial/Overview-of-Images-in-Java-game-programming/28158
                Now I will speak about how to draw an image with java. You will find a class called java.awt.Image which will hold the image. So how to get the image? We have two possibilities 1) the image is in the codebase which mean it is besides the jar or the class of the applet. 2) the image is inside the jar so we will handle it as a resourse.
1)      In the first case we can instantiate the image within an applet we will use the applet method  getImage().
Image backImgage = getImage (getCodeBase (), "back.jpg");
 
If we are in a frame we will (desktop game) we will use the ToolKit class
     
Image backImage = Toolkit.getDefaultToolkit().getImage("back.jpg");



2)      In this case the image file is compressed in the jar file so it is considered as a resource. Simple we get it with simple function getClass().getResource(pathInPackage); and the pathInPackage variable holds the path of the resource for example if we have put and image back.jpg in a package com.game.resources the path will be /com/game/resources/back.png.

Toolkit kit = Toolkit.getDefaultToolkit();        
Image backImage = kit.getImage(getClass().getResource("/game/back.jpg"));


I’ll use example 4 but I’ll modify the update method in the Animator class and the paint method in the Ball class.

public void update(Graphics g) {         
            if (dbImage == null) {
                  dbImage = createImage(this.getSize().width,this.getSize().height);
                  dbg = dbImage.getGraphics();
            }
                       
            dbg.drawImage(backImage,0, 0,this.getSize().widththis.getSize().height,this);
                       
            dbg.setColor(getForeground());
            paint(dbg);
           
            g.drawImage(dbImage, 0, 0, this);
}

                And the Ball class

public void paint(Graphics g) {          
            g.drawImage(ball,x - radiusy - radius, 2 *radius, 2 * radius,null);
           
      }





Constraints

                Wow I can nearly say that we are close to finish the basics. Now I’ll speak about some checks which you should do theme those checks are the constraints or the rules of the game. For example in the pervious example we want to make the balls do not go outside the applet, after they touch the edges it will change its movement in the other direction.
                But where do we write these constraints? From my point of view we either write them entire the object if it is specified on it for example in the move() function of the Ball class ,or write them on global level over objects for example in the run we check the list of the balls. Do not worry we will apply those two ways in the previous example.
                Ok we will make the balls do not go outside the applet, after they touch the edges it will change its movement in the other direction. we will achieve it with changing the at the move function.




public void move() {
            x += xDirection / Math.abs(xDirection) * Math.abs(speed);
            y += yDirection / Math.abs(yDirection) * Math.abs(speed);
            if(x <= radius || x+radius >=Animation5.appWidth){
                  xDirection *=-1;
            }
            if(y <= radius || y+radius >=Animation5.appHeight){
                  yDirection *=-1;
            }
}


                We really done it but we have another problem which is the balls go throw each other so we need to write some constraints which make the balls collections will change the direction of the ball. But at this point we can not write the constraints on the move() function of the Ball class. so where we can write the code? It will suitable to write it in the run method which have the list of the balls. I’ll use Pythagorean theorem to solve something like that and I’ll calculate the distance between the centers of the two balls, and we will compare the result with the sum of the two reduces of the balls.
The major changes in the Ball class

public void goOtherDirection(int dist){
            xDirection*=-1;
            yDirection*=-1;
            x+=xDirection*dist;
            y+=yDirection*dist;
      }
public void move() {
            x += xDirection / Math.abs(xDirection) * Math.abs(speed);
            y += yDirection / Math.abs(yDirection) * Math.abs(speed);
            if(x <= radius || x+radius >=Animation5.appWidth){
                  x = xDirection==-1?radius:(Animation5.appWidth-radius);
                  xDirection *=-1;
            }
            if(y <= radius || y+radius >=Animation5.appHeight){
                  y = yDirection==-1?radius:(Animation5.appHeight-radius);
                  yDirection *=-1;
            }
      }


And the major changes in the Animation class
public void run() {
            while (true) {
                  for (int i = 0; i < balls.size(); i++) {
                        Ball ball = (Ball) balls.get(i);
                        for (int j = i+1; j < balls.size(); j++) {
                              Ball oBall = (Ball) balls.get(j);
                              int difX =Math.abs(ball.getX() -                                     oBall.getX());
                              int difY =Math.abs(ball.getY() - oBall.getY());
                              int sumOfRaduces = ball.getRadius()+oBall.getRadius();
                              int dist =  difX*difX + difY*difY;
                              if(dist <=sumOfRaduces*sumOfRaduces){
                                    ball.goOtherDirection(sumOfRaduces-(int)Math.sqrt(dist));
                                    oBall.goOtherDirection(sumOfRaduces-(int)Math.sqrt(dist));
                              }
                        }
                  }
                  for (int i = 0; i < balls.size(); i++) {
                        Ball ball = (Ball) balls.get(i);
                        ball.move();
                  }
                 
                  repaint();
                  try {
                        Thread.sleep(50);
                  } catch (InterruptedException ex) {
                  }
            }
      }

                You will find code in Example 5.

No comments