FileDocCategorySizeDatePackage
TiledLayer.javaAPI DocJ2ME MIDP 2.024421Thu Nov 07 12:02:28 GMT 2002javax.microedition.lcdui.game

TiledLayer.java

/*
 * @(#)TiledLayer.java	1.49 02/07/24 @(#)
 *
 * Copyright (c) 2002 Sun Microsystems, Inc.  All rights reserved.
 * PROPRIETARY/CONFIDENTIAL
 * Use is subject to license terms.
 */

package javax.microedition.lcdui.game;

import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.Graphics;

/**
 * A TiledLayer is a visual element composed of a grid of cells that
 * can be filled with a set of
 * tile images.  This class allows large virtual layers to be created
 * without the need for an
 * extremely large Image.  This technique is commonly used in 2D
 * gaming platforms to create
 * very large scrolling backgrounds,
 * <P>
 * <h3>Tiles</h3>
 * The tiles used to fill the TiledLayer's cells are provided in a
 * single Image object which
 * may be mutable or immutable.  The Image is broken up into a series
 * of equally-sized tiles;
 * the tile size is specified along with the Image.  As shown in the
 * figure below, the same
 * tile set can be stored in several different arrangements depending
 * on what is the most
 * convenient for the game developer.  
 * <br>
 * <center><img src="doc-files/tiles.gif" width=588 height=412
 *   ALT="Tiles"></center>
 * <br>
 * Each tile is assigned a unique index number.  The tile located in
 * the upper-left corner
 * of the Image is assigned an index of 1.  The remaining tiles are
 * then numbered consecutively
 * in row-major order (indices are assigned across the first row, then
 * the second row, and so on).
 * These tiles are regarded as <em>static tiles</em> because there is
 * a fixed link between
 * the tile and the image data associated with it.
 * <P> 
 * A static tile set is created when the TiledLayer is instantiated;
 * it can also be updated
 * at any time using the {@link #setStaticTileSet} method.
 * <P>
 * In addition to the static tile set, the developer can also define
 * several <em>animated tiles</em>.
 * An animated tile is a virtual tile that is dynamically associated
 * with a static tile; the appearance
 * of an animated tile will be that of the static tile that it is
 * currently associated with.
 * <P>
 * Animated tiles allow the developer to change the appearance of a
 * group of cells
 * very easily.  With the group of cells all filled with the animated
 * tile, the appearance
 * of the entire group can be changed by simply changing the static
 * tile associated with the
 * animated tile.  This technique is very useful for animating large
 * repeating areas without
 * having to explicitly change the contents of numerous cells.
 * <P>
 * Animated tiles are created using the {@link #createAnimatedTile}
 * method, which returns the
 * index to be used for the new animated tile.  The animated tile
 * indices are always negative
 * and consecutive, beginning with -1.  Once created, the static tile
 * associated with an
 * animated tile can be changed using the {@link #setAnimatedTile}
 * method.
 * <P>
 * <h3>Cells</h3>
 * The TiledLayer's grid is made up of equally sized cells; the number
 * of rows and
 * columns in the grid are specified in the constructor, and the
 * physical size of the cells
 * is defined by the size of the tiles.  
 * <P>
 * The contents of each cell is specified by means of a tile index; a
 * positive tile index refers
 * to a static tile, and a negative tile index refers to an animated
 * tile.  A tile index of 0
 * indicates that the cell is empty; an empty cell is fully
 * transparent and nothing is drawn
 * in that area by the TiledLayer.  By default, all cells contain tile
 * index 0.
 * <P>
 * The contents of cells may be changed using {@link #setCell} and
 * {@link #fillCells}.  Several
 * cells may contain the same tile; however, a single cell cannot
 * contain more than one tile.
 * The following example illustrates how a simple background can be
 * created using a TiledLayer.
 * <br>
 * <center><img src="doc-files/grid.gif" width=735 height=193
 * ALT="TiledLayer Grid"></center>
 * <br>
 * In this example, the area of water is filled with an animated tile
 * having an index of -1, which
 * is initially associated with static tile 5.  The entire area of
 * water may be animated by simply
 * changing the associated static tile using <code>setAnimatedTile(-1,
 * 7)</code>.
 * <br>
 * <center><img src="doc-files/grid2.gif" width=735 height=193
 * ALT="TiledLayer Grid 2"></center>
 * <br>
 * <P>
 * <h3>Rendering a TiledLayer</h3>
 * A TiledLayer can be rendered by manually calling its paint method;
 * it can also be rendered
 * automatically using a LayerManager object.
 * <P>
 * The paint method will attempt to render the entire TiledLayer
 * subject to the
 * clip region of the Graphics object; the upper left corner of the
 * TiledLayer is rendered at
 * its current (x,y) position relative to the Graphics object's
 * origin.  The rendered region
 * may be controlled by setting the clip region of the Graphics object
 * accordingly.
 * <P>
 * @since MIDP 2.0
 */
public class TiledLayer extends Layer {

    /**
     * Creates a new TiledLayer.  <p>
     *
     * The TiledLayer's grid will be <code>rows</code> cells high and
     * <code>columns</code> cells wide.  All cells in the grid are initially
     * empty (i.e. they contain tile index 0).  The contents of the grid may
     * be modified through the use of {@link #setCell} and {@link #fillCells}.
     * <P>
     * The static tile set for the TiledLayer is created from the specified
     * Image with each tile having the dimensions of tileWidth x tileHeight.
     * The width of the source image must be an integer multiple of
     * the tile width, and the height of the source image must be an integer
     * multiple of the tile height; otherwise, an IllegalArgumentException
     * is thrown;<p>
     *
     * The entire static tile set can be changed using 
     * {@link  #setStaticTileSet(Image, int, int)}.
     * These methods should be used sparingly since they are both
     * memory and time consuming.
     * Where possible, animated tiles should be used instead to
     * animate tile appearance.<p>
     *
     * @param columns the width of the <code>TiledLayer</code>,
     * expressed as a number of cells
     * @param rows the height of the <code>TiledLayer</code>,
     * expressed as a number of cells
     * @param image the <code>Image</code> to use for creating
     *  the static tile set
     * @param tileWidth the width in pixels of a single tile
     * @param tileHeight the height in pixels of a single tile
     * @throws NullPointerException if <code>image</code> is <code>null</code>
     * @throws IllegalArgumentException if the number of <code>rows</code>
     *  or <code>columns</code> is less than <code>1</code>
     * @throws IllegalArgumentException if <code>tileHeight</code>
     *  or <code>tileWidth</code> is less than <code>1</code>
     * @throws IllegalArgumentException if the <code>image</code>
     *  width is not an integer multiple of the <code>tileWidth</code>
     * @throws IllegalArgumentException if the <code>image</code>
     * height is not an integer multiple of the <code>tileHeight</code>
     */
    public TiledLayer(int columns, int rows, Image image, int tileWidth,
		      int tileHeight) {
	// IllegalArgumentException will be thrown 
	// in the Layer super-class constructor
        super(columns < 1 || tileWidth < 1 ? -1 : columns * tileWidth, 
	         rows < 1 || tileHeight < 1 ? -1 : rows * tileHeight);

        // if img is null img.getWidth() will throw NullPointerException
        if (((image.getWidth() % tileWidth) != 0) || 
            ((image.getHeight() % tileHeight) != 0)) {
             throw new IllegalArgumentException();
	}
        this.columns = columns;
	this.rows = rows;

        cellMatrix = new int[rows][columns];

        int noOfFrames = 
            (image.getWidth() / tileWidth) * (image.getHeight() / tileHeight);
        // the zero th index is left empty for transparent tile
        // so it is passed in  createStaticSet as noOfFrames + 1
        // Also maintain static indices is true
	// all elements of cellMatrix[][] 
	// are set to zero by new, so maintainIndices = true
        createStaticSet(image,  noOfFrames + 1, tileWidth, tileHeight, true);
    }

    /**
     * Creates a new animated tile and returns the index that refers
     * to the new animated tile.  It is initially associated with
     * the specified tile index (either a static tile or 0).
     * <P>
     * The indices for animated tiles are always negative.  The first
     * animated tile shall have the index -1, the second, -2, etc.  
     *
     * @param staticTileIndex the index of the associated tile 
     * (must be <code>0</code> or a valid static tile index)
     * @return the index of newly created animated tile
     * @throws IndexOutOfBoundsException if the 
     * <code>staticTileIndex</code> is invalid
     */
    public int createAnimatedTile(int staticTileIndex) {
        // checks static tile 
        if (staticTileIndex < 0 || staticTileIndex >= numberOfTiles) { 
	    throw new IndexOutOfBoundsException();
	}

        if (anim_to_static == null) {
	    anim_to_static = new int[4];
	    numOfAnimTiles = 1;
        } else if (numOfAnimTiles == anim_to_static.length) {
	    // grow anim_to_static table if needed 
	    int new_anim_tbl[] = new int[anim_to_static.length * 2];
	    System.arraycopy(anim_to_static, 0, 
                         new_anim_tbl, 0, anim_to_static.length);
	    anim_to_static = new_anim_tbl;
	}
	anim_to_static[numOfAnimTiles] = staticTileIndex;
	numOfAnimTiles++;
        return (-(numOfAnimTiles - 1));
    }

    /**
     * Associates an animated tile with the specified static tile.  <p>
     *
     * @param animatedTileIndex the index of the animated tile
     * @param staticTileIndex the index of the associated tile
     * (must be <code>0</code> or a valid static tile index)
     * @throws IndexOutOfBoundsException if the 
     * <code>staticTileIndex</code> is invalid
     * @throws IndexOutOfBoundsException if the animated tile index
     * is invalid
     * @see #getAnimatedTile
     *
     */
    public void setAnimatedTile(int animatedTileIndex, int staticTileIndex) {
        // checks static tile
        if (staticTileIndex < 0 || staticTileIndex >= numberOfTiles) {  
	    throw new IndexOutOfBoundsException();
	}
        // do animated tile index check
	animatedTileIndex = - animatedTileIndex;
	if (anim_to_static == null || animatedTileIndex <= 0 
            || animatedTileIndex >= numOfAnimTiles) { 
	    throw new IndexOutOfBoundsException();
        }

        anim_to_static[animatedTileIndex] = staticTileIndex;

    }

    /**
     * Gets the tile referenced by an animated tile.  <p>
     *
     * Returns the tile index currently associated with the
     * animated tile.
     *
     * @param animatedTileIndex the index of the animated tile
     * @return the index of the tile reference by the animated tile
     * @throws IndexOutOfBoundsException if the animated tile index
     * is invalid
     * @see #setAnimatedTile
     */
    public int getAnimatedTile(int animatedTileIndex) {
        animatedTileIndex = - animatedTileIndex;
        if (anim_to_static == null || animatedTileIndex <= 0 
                   || animatedTileIndex >= numOfAnimTiles) { 
	    throw new IndexOutOfBoundsException();
        }
	
        return anim_to_static[animatedTileIndex];
    }

    /**
     * Sets the contents of a cell.  <P>
     *
     * The contents may be set to a static tile index, an animated
     * tile index, or it may be left empty (index 0)
     * @param col the column of cell to set
     * @param row the row of cell to set
     * @param tileIndex the index of tile to place in cell
     * @throws IndexOutOfBoundsException if there is no tile with index
     *         <code>tileIndex</code>
     * @throws IndexOutOfBoundsException if <code>row</code> or
     *         <code>col</code> is outside the bounds of the 
     *         <code>TiledLayer</code> grid
     * @see #getCell
     * @see #fillCells
     */
    public void setCell(int col, int row, int tileIndex) {

        if (col < 0 || col >= this.columns || row < 0 || row >= this.rows) {
            throw new IndexOutOfBoundsException();
        }

	if (tileIndex > 0) {
            // do checks for static tile 
            if (tileIndex >= numberOfTiles) { 
	        throw new IndexOutOfBoundsException();
	    }
	} else if (tileIndex < 0) {
            // do animated tile index check
	    if (anim_to_static == null ||
                (-tileIndex) >= numOfAnimTiles) { 
	        throw new IndexOutOfBoundsException();
            }
	}

        cellMatrix[row][col] = tileIndex;
 
    }

    /**
     * Gets the contents of a cell.  <p>
     *
     * Gets the index of the static or animated tile currently displayed in
     * a cell.  The returned index will be 0 if the cell is empty.
     *
     * @param col the column of cell to check
     * @param row the row of cell to check
     * @return the index of tile in cell
     * @throws IndexOutOfBoundsException if <code>row</code> or
     *         <code>col</code> is outside the bounds of the 
     *         <code>TiledLayer</code> grid
     * @see #setCell
     * @see #fillCells
     */
    public int getCell(int col, int row) {
        if (col < 0 || col >= this.columns || row < 0 || row >= this.rows) {
            throw new IndexOutOfBoundsException();
        }
        return cellMatrix[row][col];
    }

    /**
     * Fills a region cells with the specific tile.  The cells may be filled
     * with a static tile index, an animated tile index, or they may be left 
     * empty (index <code>0</code>). 
     *
     * @param col the column of top-left cell in the region
     * @param row the row of top-left cell in the region
     * @param numCols the number of columns in the region
     * @param numRows the number of rows in the region
     * @param tileIndex the Index of the tile to place in all cells in the 
     * specified region
     * @throws IndexOutOfBoundsException if the rectangular region
     *         defined by the parameters extends beyond the bounds of the
     *         <code>TiledLayer</code> grid
     * @throws IllegalArgumentException if <code>numCols</code> is less
     * than zero
     * @throws IllegalArgumentException if <code>numRows</code> is less
     * than zero
     * @throws IndexOutOfBoundsException if there is no tile with
     *         index <code>tileIndex</code>
     * @see #setCell
     * @see #getCell
     */
    public void fillCells(int col, int row, int numCols, int numRows,
                          int tileIndex) {

        if (col < 0 || col >= this.columns || row < 0 || row >= this.rows ||
                   numCols < 0 || col + numCols > this.columns ||
                   numRows < 0 || row + numRows > this.rows) {
            throw new IndexOutOfBoundsException();
        }

	if (tileIndex > 0) {
            // do checks for static tile 
            if (tileIndex >= numberOfTiles) { 
	        throw new IndexOutOfBoundsException();
	    }
	} else if (tileIndex < 0) {
            // do animated tile index check
	    if (anim_to_static == null || 
                (-tileIndex) >= numOfAnimTiles) { 
	            throw new IndexOutOfBoundsException();
            }
	}

        for (int rowCount = row; rowCount < row + numRows; rowCount++) {
            for (int columnCount = col; 
                     columnCount < col + numCols; columnCount++) {
                cellMatrix[rowCount][columnCount] = tileIndex;
            }
        }
    }


    /**
     * Gets the width of a single cell, in pixels.
     * @return the width in pixels of a single cell in the 
     * <code>TiledLayer</code> grid
     */
    public final int getCellWidth() {
        return cellWidth;
    }

    /**
     * Gets the height of a single cell, in pixels.
     * @return the height in pixels of a single cell in the 
     * <code>TiledLayer</code> grid
     */
    public final int getCellHeight() {
        return cellHeight;
    }

    /**
     * Gets the number of columns in the TiledLayer grid. 
     * The overall width of the TiledLayer, in pixels, 
     * may be obtained by calling {@link #getWidth}.
     * @return the width in columns of the 
     * <code>TiledLayer</code> grid
     */
    public final int getColumns() {
        return columns;
    }

    /**
     * Gets the number of rows in the TiledLayer grid.  The overall
     * height of the TiledLayer, in pixels, may be obtained by
     * calling {@link #getHeight}.
     * @return the height in rows of the 
     * <code>TiledLayer</code> grid
     */
    public final int getRows() {
        return rows;
    }

    /**
     * Change the static tile set.  <p>
     *
     * Replaces the current static tile set with a new static tile set.
     * See the constructor {@link #TiledLayer(int, int, Image, int, int)}
     * for information on how the tiles are created from the
     * image.<p>
     *
     * If the new static tile set has as many or more tiles than the
     * previous static tile set,
     * the the animated tiles and cell contents will be preserve.  If
     * not, the contents of
     * the grid will be cleared (all cells will contain index 0) and
     * all animated tiles
     * will be deleted.
     * <P>
     * @param image the <code>Image</code> to use for creating the
     * static tile set
     * @param tileWidth the width in pixels of a single tile
     * @param tileHeight the height in pixels of a single tile
     * @throws NullPointerException if <code>image</code> is <code>null</code>
     * @throws IllegalArgumentException if <code>tileHeight</code>
     *  or <code>tileWidth</code> is less than <code>1</code>
     * @throws IllegalArgumentException if the <code>image</code>
     *  width is not an integer  multiple of the <code>tileWidth</code>
     * @throws IllegalArgumentException if the <code>image</code>
     *  height is not an integer  multiple of the <code>tileHeight</code>
     */
    public void setStaticTileSet(Image image, int tileWidth, int tileHeight) {
        // if img is null img.getWidth() will throw NullPointerException
        if (tileWidth < 1 || tileHeight < 1 ||
	    ((image.getWidth() % tileWidth) != 0) || 
            ((image.getHeight() % tileHeight) != 0)) {
             throw new IllegalArgumentException();
	}
        setWidthImpl(columns * tileWidth);
	setHeightImpl(rows * tileHeight);

        int noOfFrames = 
          (image.getWidth() / tileWidth) * (image.getHeight() / tileHeight);

        // the zero th index is left empty for transparent tile
        // so it is passed in  createStaticSet as noOfFrames + 1

	if (noOfFrames >= (numberOfTiles - 1)) {
	    // maintain static indices
	    createStaticSet(image, noOfFrames + 1, tileWidth, tileHeight, true);
	} else {
            createStaticSet(image, noOfFrames + 1, tileWidth, 
                                tileHeight, false);
	}	
    }

    /**
     * Draws the TiledLayer.  
     *
     * The entire TiledLayer is rendered subject to the clip region of
     * the Graphics object.
     * The TiledLayer's upper left corner is rendered at the
     * TiledLayer's current
     * position relative to the origin of the Graphics object.   The current
     * position of the TiledLayer's upper-left corner can be retrieved by 
     * calling {@link #getX()} and {@link #getY()}.
     * The appropriate use of a clip region and/or translation allows
     * an arbitrary region
     * of the TiledLayer to be rendered.
     * <p>
     * If the TiledLayer's Image is mutable, the TiledLayer is rendered 
     * using the current contents of the Image.
     * @param g the graphics object to draw the <code>TiledLayer</code>
     * @throws NullPointerException if <code>g</code> is <code>null</code>
     */
    public final void paint(Graphics g) {

        if (g == null) {
            throw new NullPointerException();
        }

        if (visible) {
	    int tileIndex = 0;

	    // y-coordinate
	    int ty        = this.y;
            for (int row = 0; 
                   row < cellMatrix.length; row++, ty += cellHeight) {

	        // reset the x-coordinate at the beginning of every row
                // x-coordinate to draw tile into
	        int tx = this.x;
		int totalCols = cellMatrix[row].length;

                for (int column = 0; column < totalCols; 
		    column++, tx += cellWidth) {
		
                    tileIndex = cellMatrix[row][column];
	            // check the indices 
		    // if animated get the corresponding 
		    // static index from anim_to_static table
		    if (tileIndex == 0) { // transparent tile
			continue;
                    } else if (tileIndex < 0) {
                        tileIndex = getAnimatedTile(tileIndex);
		    }

		    g.drawRegion(sourceImage, 
				 tileSetX[tileIndex], 
				 tileSetY[tileIndex], 
				 cellWidth, cellHeight,
				 Sprite.TRANS_NONE,
				 tx, ty,
				 Graphics.TOP | Graphics.LEFT);

                }
            }
	}
    }

    // private implementationv

    /**
     * create the Image Array.
     *
     * @param image Image to use for creating the static tile set
     * @param noOfFrames total number of frames
     * @param tileWidth The width, in pixels, of a single tile
     * @param tileHeight The height, in pixels, of a single tile
     * @param maintainIndices 
     */

    private void createStaticSet(Image image, int noOfFrames, int tileWidth, 
                      int tileHeight, boolean maintainIndices) {

        cellWidth = tileWidth;
        cellHeight = tileHeight;

	int imageW = image.getWidth();
	int imageH = image.getHeight();

	sourceImage = image;

	numberOfTiles = noOfFrames;
	tileSetX = new int[numberOfTiles];
	tileSetY = new int[numberOfTiles];
	
	if (!maintainIndices) {
            // populate cell matrix, all the indices are 0 to begin with
            for (rows = 0; rows < cellMatrix.length; rows++) {
                int totalCols = cellMatrix[rows].length;
                for (columns = 0; columns < totalCols; columns++) {
                    cellMatrix[rows][columns] = 0;
                }
            }
	    // delete animated tiles
	    anim_to_static = null;
	} 

        int currentTile = 1;

        for (int y = 0; y < imageH; y += tileHeight) {
            for (int x = 0; x < imageW; x += tileWidth) {

		tileSetX[currentTile] = x;
		tileSetY[currentTile] = y;

                currentTile++;
            }
        }
    }

    /** 
     * the overall height of the TiledLayer grid
     */
    private int cellHeight; // = 0;
    /** 
     * the overall cell width of the TiledLayer grid
     */
    private int cellWidth; // = 0;

    /** 
     * The num of rows of the TiledLayer grid.
     */
    private int rows; // = 0;

    /** 
     * the num of columns in the TiledLayer grid
     */
    private int columns; // = 0;

    /** 
     * int array for storing row and column of cell
     *
     * it contains the tile Index for both static and animated tiles
     */
    private int[][] cellMatrix; // = null;

    /**
     * Source image for tiles
     */
    // package access as it is used by Pixel level Collision
    // detection with a Sprite
    Image sourceImage; // = null;
    
    /**
     * no. of tiles
     */
    private int numberOfTiles; // = 0;

    /**
     * X co-ordinate definitions for individual frames into the source image
     */
    // package access as it is used by Pixel level Collision
    // detection with a Sprite
    int[] tileSetX;

    /**
     * Y co-ordinate definitions for individual frames into the source image
     */ 
    // package access as it is used by Pixel level Collision
    // detection with a Sprite
    int[] tileSetY;
    
    /** 
     * Table to map from animated Index to static Index
     * 0th location is unused.
     * anim --> static Index
     * -1 --> 21
     * -2 --> 34
     * -3 --> 45
     * for now keep 0 the location of the table empty instead of computing
     * -index make index +ve and access this Table.
     *  
     */
    private int[] anim_to_static; // = null;

    /** 
     * total number of animated tiles. This variable is also used as 
     * index in the above table to add new entries to the anim_to_static table.
     * initialized to 1 when table is created.
     */
    private int numOfAnimTiles; // = 0

}