/** * <p>Title: TiledLayer.java</p> * * <p>Description: TiledLayer MIDP2.0</p> * * <p>Copyright: Copyright (c) 2009</p> * * @author fengsheng.yang * * @version 1.0 * * @Date 2009-5-14 */ package javax.microedition.lcdui.game; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Rect; public class TiledLayer extends Layer { /** * 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 Bitmap 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 /** * 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, Bitmap 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; } /** * 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(Bitmap 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++; } } } /** * 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(Canvas canvas) { if (canvas == 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); } drawImage(canvas, tx, ty, sourceImage, tileSetX[tileIndex], tileSetY[tileIndex], cellWidth, cellHeight); } } } } private void drawImage(Canvas canvas, int x, int y, Bitmap bsrc, int sx, int sy, int w, int h) { Rect rect_src = new Rect(); rect_src.left = sx; rect_src.right = sx + w; rect_src.top = sy; rect_src.bottom = sy + h; Rect rect_dst = new Rect(); rect_dst.left = x; rect_dst.right = x + w; rect_dst.top = y; rect_dst.bottom = y + h; canvas.drawBitmap(bsrc, rect_src, rect_dst, null); rect_src = null; rect_dst = null; } /** * 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(Bitmap 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); } } }