Docstoc

week 9

Document Sample
week 9 Powered By Docstoc
					Week 9: Running a games application

ESSENTIAL READING

These Notes
Charatan & Kans


Learning objectives

By the end of this chapter you should be able to:


      set the sequence of objects to be inserted in a panel;
      load an image from a text-file;
      explain the benefits of text-file rendering.




Introduction


Often when building a game or even a simple animation the animation flow is the hardest
thing to control. As explained in previous chapters sometimes this control can be
established by existing Java2D available tools like the zapImageParts() method we
discussed last week. However, even with these tools there is an important part of
animation (especially for games) that is not readily available in any java package. This is
the animated objects interaction. That is when you have two or more objects that need to
perform a certain action depending to one another.

These can be controlled using text files which can dictate the flow of the images which
are to be rendered into the buffer (using BufferedImage object). This can be achieved
by assigning a specialized object that would load the different images to a specilaised
image player. The Image Loader can be control by a text file that has a control of each
image type (sequence in one image, multiple image files or a single image). Each image
could have character that could identify the specific image type that is to be loaded.




                                                                                        35
Here we will assign each type of image file with its own format. The format is
established by the first character on the line in the text file. After establishing the type of
image from the text file that is to be accessed get the name of the image(s) and acquire
the actual image file to be rendered. This format of using text files to load the images and
control animation would also tidy up the code and allow easy access to the animation
mechanism by creating object that will be dedicated to animate a single (a group of
images) images. The preferred formats of each ImagesFile are described in table
10.1:



Table 10.1
o < fnm >                           a single image file
n < fnm *.ext> <number>             a series of numbered image files, whose filenames use
                                    the numbers 0 -<number>-1
s < fnm > <number>                  a strip file (fnm) containing a single row of <number>
                                    image that could be divided to create animation
g <name> <fnm> [ <fnm> ]*           A group of files with different names. They are accessible
                                    via <name> and position or <fnm> prefix and blank lines
                                    and comment lines.


The numbered image files (n) can be accessed by the fnm prefix and number (states the
number of images to be threaded to create the animation). The strip (s) file images can be
accessed by the <fnm> prefix and their position inside the file (which is assumed to hold
a single row of multiple images).The images in group files can be accessed by the 'g'
<name> and the <fnm> prefix of the particular file, or its position in the group. The
group files are a little bit more different from the rest (the n, s and o). The information of
accessing these file will be explained in detail through out this chapter.



9.1 The Class to load the images (ImagesLoader)

The ImagesLoader class can load images in four different formats, which we call ‘o’,
‘n’, ’s’, and 'g' images. These images are loaded as buffered images using ImageIO
class’s read() method. This means that these images are stored as BufferedImage
objects, so they can be manipulated as ‘managed’ images by the JVM and ofcourse make
a smooth flow of animation by preloading all the image objects into the buffer and thus
make the images ready to be loaded to the screen.

The ImagesLoader object normally implemented by supplying it with a configuration
file containing the filenames of the required images to be loaded before game play
begins. This allows the images to be easily obtained from its original location and be sent
to the buffer. This approach also helps to determine faulty animations. This is realized



                                                                                            36
when a non-existing image is being loaded. Thus the error will be determined before the
game started running*.

* This is a runtime error however the program will compile but the animation should not start

This concept is shown on the ImagesLoader constructor bellow:



  public ImagesLoader(String fnm)// begin by loading the images specified in fnm
  {
    initLoader();
    loadImagesFile(fnm);
  }




This constructor receives the required text-file name and pass it to the initLoader()
method to initialize the loading of the required image files. The initLoader()
method set up the graphics environment according to the local graphics configuration
(graphics card in the OS) making sure the rendering speed and quality matches the
available resources (processor and memory). This is done by declaring the
GraphicsConfiguration attribute in the global variables and initializes it to the
GraphicsEnvironment that will get the default graphic capability of the current
machine.


private void initLoader()
  {
    imagesMap = new HashMap();
    gNamesMap = new HashMap();

      GraphicsEnvironment ge
       GraphicsEnvironment.getLocalGraphicsEnvironment();

      gc = ge.getDefaultScreenDevice().getDefaultConfiguration();
  }



The other attribute to be initialized is the array container which is a HashMap file that is
used to save the stream of the buffered images. This attribute is a global variable so that
the images are saved once and accessed by all the method when this attribute of the
ImageLoader is been initialized using the initLoader() method. Here two type
of HashMap are required. As we specified earlier all the file type are similar except for
the g type and consequently we require one collection to deal with all the files and one
special map to deal with the g type file.




                                                                                                37
//global variable
  private HashMap imagesMap;
  private HashMap gNamesMap;




The imagesMap

The key in this hash map is the filename prefix (the first letter) and the object (value) is
an ArrayList of images managed by BufferedImage object associated with that
file name. The exact meaning of 'name' varies depending on the type of image that was
loaded. For example:


      An 'o' image (e.g. "o atomic.gif"), the name is the filename minus its extension
       (i.e. "atomic") and the ArrayList holds just a single image.

      An 'n' image (e.g. "n numbers*.gif 6"), the name is the part of the filename before
       the * (i.e. "numbers"), and the ArrayList holds several images (6 in this case).

      A 'g' image (e.g. "g fighter left.gif right.gif still.gif up.gif"), the name is the string
       after the 'g' character (i.e. "fighter"), and the ArrayList is as large as the sequence
       of filenames given (in this case 4)


The gNamesMap

The key is the 'g' <name>, the object is an ArrayList of filename prefixes for the
group. This is used to access a group image by its 'g' name and filename. This is needed
as the loading of 'g' images also causes updates to the gNamesMap HashMap. Its key is
the 'g' name (e.g. "fighter") that is required to paint a fighter that is able to jump and turn
left and right , but its value is an ArrayList of filename Strings (minus their
extensions). For instance, the 'fighter' name has an ArrayList associated with it
holding the strings "left", "right", "still", and "up". With this explanation the need for
extra collectionlist of a g type file can clearly be seen. The theImages.txt configuration
file example is shown below:




                                                                                              38
The loadImagesFile(String fnm)method

The constructor also holds a method loadImagesFile(String fnm) which takes
the file name and load the sequence of the specified images. This method gets the name
of the text file, go to a specified folder and fetch the images and load them according to
their type. For example if the folder name was Images and the file name called
theImages.txt here is the code that would load the images:



private void loadImagesFile(String fnm)
  {
    String IMAGE_DIR = “/Images”; //should be declared global and final
    String imsFNm = IMAGE_DIR + fnm;

    try
    {
      InputStream in = this.getClass().getResourceAsStream(imsFNm);
      BufferedReader br =new BufferedReader( new InputStreamReader(in));

      String line;
      char ch;

      while((line = br.readLine()) != null)
      {
         if (line.length() == 0) // blank line
           continue;
         if (line.startsWith("//"))   // comment
           continue;



                                                                                       39
          ch = Character.toLowerCase( line.charAt(0) );
          if (ch == 'o') // a single image
            getFileNameImage(line);
          else if (ch == 'n') // a numbered sequence of images
            getNumberedImages(line);
          else if (ch == 's') // an images strip
            getStripImages(line);
          else if (ch == 'g') // a group of images
            getGroupImages(line);
          else
            System.out.println("Do not recognize line: " + line);
        }
        br.close();
      }
      catch (IOException e)
      { System.out.println("Error reading file: " + imsFNm);
        System.exit(1);
      }
  }




In the while loop, the iteration starts by checking blank lines, and lines beginning with '//'
to be ignored by the loader. The rest of the code checks the prefix of the file type and
pass it to appropriate method to load that specific type of image.



9.2 The ImageLoader’s for getting and actually loading the images into the
     buffer


The first step on these method is to set a tokenizer that can count the number of
arguments on the file and return the error if there are wrong number of arguments on
specified type (so the formats stay correct all the time). As can be seen this method do not
really load any image all it does is fetch the image file from a folder provide a method of
distinguishing the different type of images from the file. The actual method that does
these functions are getGroupImages(line)etc.

An 'o' line causes a single filename, called <fnm>, to be loaded from Images/. So the
loadImagesFile(String fnm) will call the getFileNameImage(String
line)to load the file.



private void getFileNameImage(String line)
  {
    StringTokenizer tokens = new StringTokenizer(line);

      if (tokens.countTokens() != 2)
      {


                                                                                           40
        System.out.println("Wrong no. of arguments for " + line);
      }
      else
      {
        tokens.nextToken();    // skip command label
        System.out.print("o Line: ");
        loadSingleImage(tokens.nextToken());
      }
  }




Essentially this method only check if the line is correct and get the file name to be loaded
and as what format (e.g. number of images). The actual method that loads the images if
the loadSingleImage(String fnm).



public boolean loadSingleImage(String fnm)// can be called directly
  {
    String name = getPrefix(fnm);

      if (imagesMap.containsKey(name))
      {
        System.out.println( "Error: " + name + "already used");
        return false;
      }

      BufferedImage bi = loadImage(fnm);
      if (bi != null)
      {
        ArrayList imsList = new ArrayList();
        imsList.add(bi);
        imagesMap.put(name, imsList);
        System.out.println(" Stored " + name + "/" + fnm);
        return true;
      }
      else
      {
        return false;
      }
  }




This     method       uses   two      important    methods      getPrefix(fnm)           and
loadImage(fnm). The get prefix method has only one job. The job is to extract name
before '.' of the filename e.g. if the image name is fighter.gif the method only get fighter
and discard the .gif suffix. This has significant reason but will is beyond the scope of this
lesson.




                                                                                          41
private String getPrefix(String fnm)
  {
    int posn;
    if ((posn = fnm.lastIndexOf(".")) == -1) {
      System.out.println("No prefix found for filename: " + fnm);
      return fnm;
    }
    else
      return fnm.substring(0, posn);
  }




To load the image from theImage.txt and returning it as a BufferedImage which is
compatible with the graphics device being used with current machine the method
loadImage(fnm)is used. This ImageIO is used to get the image. The rest of the
components on this method have been covered last week.


 public BufferedImage loadImage(String fnm)
   {
     try
     {
       BufferedImage im = ImageIO.read(
                      getClass().getResource(IMAGE_DIR + fnm) );

        int transparency = im.getColorModel().getTransparency();

        BufferedImage copy = gc.createCompatibleImage(
                           im.getWidth(), im.getHeight(),transparency );

        // create a graphics context
        Graphics2D g2d = copy.createGraphics();

        // copy image
        g2d.drawImage(im,0,0,null);
        g2d.dispose();
        return copy;
      }
      catch(IOException e)
      {
        System.out.println("Load Image error for " + fnm + ":\n" + e);
        return null;
      }
  }



The grouped images

This is a different format as of the above type of image. The group images are the most
basic image rendering for animation. It requires different control to what we have seen
earlier. This is normally specified as a series of images with different names. After being


                                                                                        42
loaded, the images will be accessible using a positional notation or by means of their
filenames (again minus the extension). The format of the group ‘g’ file is:


              g <name> <fnm> [ <fnm> ]*.


Where ‘g’ specifies the animation type, name specifies the name of the file. For example,
the 'fighter' 'g' images are defined as:

             g fighter left.gif right.gif still.gif up.gif

The method to access this file and load to the buffer is:



private void getGroupImages(String line)
  {
    StringTokenizer tokens = new StringTokenizer(line);

      if (tokens.countTokens() < 3)
      {
        System.out.println("Wrong no. of arguments for " + line);
      }
      else
      {
        tokens.nextToken();    // skip command label
        System.out.print("g Line: ");

          String name = tokens.nextToken();

          ArrayList fnms = new ArrayList();
          fnms.add( tokens.nextToken() ); // read filenames
          while (tokens.hasMoreTokens())
            fnms.add( tokens.nextToken() );

          loadGroupImages(name, fnms);
      }
  }




The method loadGroupImages(String name, ArrayList fnms) accepts
the collection of the group images which can be called directly to load these images,
whose filenames are stored in the ArrayList <fnms>. They series of images will be stored
under the 'g' name <name>.


  public int loadGroupImages(String name, ArrayList fnms)
  {
    if (imagesMap.containsKey(name))
    {



                                                                                       43
          System.out.println( "Error: " + name + "already used");
          return 0;
      }

      if (fnms.size() == 0)
      {
        System.out.println("List of filenames is empty");
        return 0;
      }

      BufferedImage bi;
      ArrayList nms = new ArrayList();
      ArrayList imsList = new ArrayList();
      String nm, fnm;
      int loadCount = 0;

      for (int i=0; i < fnms.size(); i++) // load the files
      {
        fnm = (String) fnms.get(i);
        nm = getPrefix(fnm);
        if ((bi = loadImage(fnm)) != null)
        {
          loadCount++;
          imsList.add(bi);
          nms.add( nm );
          System.out.print(nm + "/" + fnm + " ");
        }
      }
      System.out.println();

      if (loadCount == 0)
        System.out.println("No images loaded for " + name);
      else {
        imagesMap.put(name, imsList);
        gNamesMap.put(name, nms);
      }

      return loadCount;
  }



Subsequently, the image right.gif can be accessed using the number 1 or the string
"right".

As can be seen from the given methods all the ImagesLoader class does is load the
images. The actual task here is for images to be played (animated) for this an
ImagesPlayer class can be implemented. Other method of the ImagesLoader
classmethods will be discussed next week when explaining the Sprite and strips the ‘s’
type files.




                                                                                   44
9. 3 The ImagesPlayer class and its function


ImagesPlayer class is aimed at displaying the sequence of images making up a 'n', 's', or
'g' in the image file, when they are being loaded by the ImagesLoader. An
ImagePlayer class animates a single animation (image file).

The ImagesPlayer constructor is supplied with the intended duration for showing the
entire sequence of the specified animation. This value is used to calculate the amount of
time each image should be shown before the next image is displayed is a single animation.
The animation period input argument states how often the ImagesPlayer's updates the tick
(clock resolution covered on week 7).



public ImagesPlayer(String nm,int ap,double d,boolean ir,ImagesLoader il)
  {
    imName = nm;
    animPeriod = ap;
    seqDuration = d;
    isRepeating = ir;
    imsLoader = il;

      animTotalTime = 0L;

      if (seqDuration < 0.5)
      {
        System.out.println("Warning: minimum seq duration is 0.5 sec.");
        seqDuration = 0.5;
      }

      if (!imsLoader.isLoaded(imName))
      {
        System.out.println(imName + " is not known by the ImagesLoader");
        numImages = 0;
        imPosition = -1;
        ticksIgnored = true;
      }
      else
      {
        numImages = imsLoader.loadImages(imName);
        imPosition = 0;
        ticksIgnored = false;
        showPeriod = (int) (1000 * seqDuration / numImages);
      }
  }




The intention is that updateTick() will be called periodically from the update()
method in the top-level animation framework. The current animation time is calculated



                                                                                      45
when updateTick() is called, and used to calculate imposition. The imposition
is a global variable that specifies which image should be returned when
getCurrentImage() is called.



public BufferedImage getCurrentImage()
  {
   if (numImages != 0)
    {
      return imsLoader.getImage(imName, imPosition);
    }
    else
    {
      return null;
    }
  }




The getCurrentImage() method checks if there are images available in the buffer if
not uses the getImage() from the ImagesLoader class to load the required images.


public void updateTick()
  {
    if (!ticksIgnored)
    {
 // update total animation time, calculated resolution
    animTotalTime = (animTotalTime + animPeriod)                   %   (long)(1000     *
                       seqDuration);

          // calculate current displayable image position
          imPosition = (int) (animTotalTime / showPeriod);
          if ((imPosition == numImages-1) && (!isRepeating))//end of sequence
          {
            ticksIgnored = true;   // stop at this image
            if (watcher != null)
            {
              watcher.sequenceEnded(imName);
            }
          }
      }
  }



The update tick does the hard work of making sure the animation is performed as specified
UPS and achieved the satisfactory FPS (these were discussed in detail in precious weeks).



public int getCurrentPosition()



                                                                                      46
  {
        return imPosition;
  }




The ImagesPlayer can be set to cycle, stop, resume, or restart at a given image position.
When an ImagesPlayer gets to the end of a sequence, it can call sequenceEnded()
in a listener. When the sequence finishes, a callback, sequenceEnded(), can be
invoked in a specified object implementing the ImagesPlayerWatcher interface.



9.4 The images player monitor interface

This used to stop the animation of certain images. It is declared interface due to it wide
usage requirement.


public interface ImagesPlayerWatcher
{
    void sequenceEnded(String imageName);
}




                                                                                       47
Tutorial Questions


   1. Discuss the importance of using file for loading images for animation.
   2. Write a method that would allow the game to be paused and resume to specific
      image call this method resumeAt() and it should accept an integer value of the image
      to started at.



Practical Questions

   1. Emplement the method on the given ImagesLoader class
   2. you will be given some images files use previous week classes and today’s to
      animate the images




                                                                                       48

				
DOCUMENT INFO
Shared By:
Categories:
Tags:
Stats:
views:3
posted:12/15/2011
language:
pages:14