Chủ Nhật, 2 tháng 9, 2012

Tải dữ liệu Tile Map từ một file

Có một trò chơi loại giải đố trong các ví dụ demo của NetBeans. Đó là trò đẩy hộp. Trò chơi này cho bạn ví dụ về load dữ liệu map từ file text. Nhưng thứ mà tôi nhớ nhất đó là code game đó làm tôi hoa hết cả mắt, heh.

Sau đây là giải pháp của tôi về việc này. Thay vì load dữ liệu map từ text file chúng ta sẽ load từ file dạng nhị phân.

Bài này sử dụng tiếp code của bài trước Dùng lớp TiledLayer hiển thị tilemap. Nếu chưa học qua bạn nên bắt đầu từ bài đó trước và hãy nắm chắc trước khi sang bài này.

Bạn hãy down map dạng SMP sau về. Đó là map tạo từ phần mềm Simple Tile Map Editor của Devlin.
samplemap.smp (155 bytes) - Download

Tạo folder maps trong folder src và save map vào đó.

Bạn có thể mở ra xem, sửa map bằng phần mềm của Devlin, download tại đây.

Để xem trước map, bấm nút Open và chọn đến file map trên bạn vừa down về. Kế tiếp, chọn tileset1.png là file tileset của bạn (nếu chưa biết là gì thì hãy đọc bài trước để biết thêm chi tiết). Bạn sẽ thấy như sau:
Click to Enlarge

Khi đã xong mọi việc, bắt tay vào chỉnh sửa code lớp clsCanvas. Thêm phương thức sau vào trước phương thức loadBlockMap( ):

    public TiledLayer getMap(String fpath, Image ftiles){
        TiledLayer tMap = null;
        try {
            // open the file
            InputStream is = this.getClass().getResourceAsStream(fpath);
            DataInputStream ds = new DataInputStream(is);
            try {
                // skip the descriptor
                ds.skipBytes(8);
                
                // read map width
                int mW = ds.readByte();
                
                // read map height
                int mH = ds.readByte();
                
                // read tile width
                int tW = ds.readByte();
                
                // read tile height
                int tH = ds.readByte();
                
                // create a new tiled layer
                tMap = new TiledLayer(mW, mH, ftiles, tW, tH);
                
                // loop through the map data
                for (int rCtr = 0; rCtr < mH; rCtr++){
                  for (int cCtr = 0; cCtr < mW; cCtr++){
                    // read a tile index 
                    byte nB = ds.readByte();  
                    
                    // if tile index is non-zero
                    // tile index 0 is usually a blank tile
                    if (nB > 0) {
                        // assign (tile index + 1) to the current cell
                        // TiledLayer objects start tile index at 1
                        // instead of 0
                        tMap.setCell(cCtr, rCtr, nB + 1);  
                    }
                  }  
                }

           } catch (Exception ex) {
               tMap = null;
               System.err.println("map loading error : " + ex.getMessage());
           }
           // close the file
           ds.close();
           ds = null;
           is = null;
        } catch (Exception ex) {
            tMap = null;
            System.err.println("map loading error : " + ex.getMessage());
        }
        
        // return the newly created map or null if loading failed
        return tMap;
    }

Bấm Alt+Shift+F nếu dùng NetBeans để import các statements còn thiếu.

Chúng ta thêm một phương thức nữa là loadBlockMapFile( ) dùng getMap( ) để load dữ liệu map lên và khởi tạo cho tiled layer blockMap. Việc này giúp bạn bảo vệ phương thức loadBlockMap( ) để tham khảo sau. Thêm đoạn code sau vào dưới phương thức loadBlockMap( ):

    public void loadBlockMapFile(){
        //initialize blockMap
        blockMap = getMap("/maps/samplemap.smp", imgTileset);
    }

Giờ ta thay lời gọ phương thức loadBlockMap( ) trong phương thức load( ) bằng loadBlockMapFile( )
        loadRoadMap();
        loadBlockMapFile();
        
    }

Bạn chạy thử chương trình sẽ thấy như sau:
Screen shot of project running in the emulator

Map này chỉ khác với ví dụ trước ở vị trí chướng ngại vật và màu sắc, không có gì đáng kể.

Sau đây là clsCanvas hoàn chỉnh cho các bạn tham khảo:
package MyGame;

import java.io.DataInputStream;
import java.io.InputStream;
import java.util.Random;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.game.GameCanvas;
import javax.microedition.lcdui.game.Sprite;
import javax.microedition.lcdui.game.TiledLayer;

public class clsCanvas extends GameCanvas implements Runnable {
// key repeat rate in milliseconds
public static final int keyDelay = 250;    

//key constants
public static final int upKey = 0;
public static final int leftKey = 1;
public static final int downKey = 2;
public static final int rightKey = 3;
public static final int fireKey = 4;

//key states for up, left, down, right, and fire key
private boolean[] isDown = {
    false, false, false, false, false
};
//last time the key changed state
private long[] keyTick = {
    0, 0, 0, 0, 0
};
//lookup table for key constants :P
private int[] keyValue = {
    GameCanvas.UP_PRESSED, GameCanvas.LEFT_PRESSED,
    GameCanvas.DOWN_PRESSED, GameCanvas.RIGHT_PRESSED, 
    GameCanvas.FIRE_PRESSED
};

private boolean isRunning = true;    
private Graphics g;
private midMain fParent;

private Image imgVan;
private Sprite Van;

private int vanSpeed = 2;

private Image imgTileset;
private TiledLayer roadMap;
private TiledLayer blockMap;

    /** Creates a new instance of clsCanvas */
    public clsCanvas(midMain m) {
        super(true);
        fParent = m;
        setFullScreenMode(true);
    }
    
    public void start(){
        Thread runner = new Thread(this);
        runner.start();
    }
    
    public void loadRoadMap(){
        //initialize the roadMap
        roadMap = new TiledLayer(11, 13, imgTileset, 16, 16);
        
        // Create a new Random for randomizing numbers
        Random Rand = new Random();
        
        //loop through all the map cells
        for (int y = 0; y < 13; y++){
            for (int x = 0; x < 11; x++){
                // get a random tile index between 2 and 5
                int index = (Math.abs(Rand.nextInt()>>>1) % (3)) + 2;
                
                // set the tile index for the current cell
                roadMap.setCell(x, y, index);
            }
        }
        
        Rand = null;
    }
    
    public void loadBlockMap(){
        // define the tile indexes to be used for each map cell
        byte[][] blockData = {
            {10, 8 , 7 , 6 , 10, 9 , 8 , 7 , 6 , 10, 9 },  
            {6 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 8 },  
            {7 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 7 },  
            {8 , 0 , 0 , 10, 6 , 0 , 0 , 7 , 0 , 0 , 6 },  
            {9 , 0 , 0 , 0 , 0 , 0 , 0 , 8 , 0 , 0 , 10},  
            {10, 0 , 0 , 0 , 0 , 0 , 0 , 9 , 0 , 0 , 9 },  
            {6 , 0 , 0 , 8 , 0 , 0 , 0 , 0 , 0 , 0 , 8 },  
            {7 , 0 , 0 , 7 , 0 , 0 , 0 , 0 , 0 , 0 , 7 },  
            {8 , 0 , 0 , 6 , 0 , 0 , 0 , 10, 0 , 0 , 6 },  
            {9 , 0 , 0 , 10, 0 , 0 , 7 , 6 , 0 , 0 , 10},  
            {10, 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 9 },  
            {6 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 8 },  
            {7 , 8 , 9 , 10, 6 , 7 , 8 , 9 , 10, 6 , 7 }
        };
        
        //initialize blockMap
        blockMap = new TiledLayer(11, 13, imgTileset, 16, 16);
        
        //loop through all the map cells
        for (int y = 0; y < 13; y++){
            for (int x = 0; x < 11; x++){
                // set the tile index for the current cell
                // take note of the reversed indexes for blockData
                blockMap.setCell(x, y, blockData[y][x]);
            }
        }
        
        blockData = null;
    }
    
    public void loadBlockMapFile(){
        //initialize blockMap from a binary file
        blockMap = getMap("/maps/samplemap.smp", imgTileset);
    }

    public TiledLayer getMap(String fpath, Image ftiles){
        TiledLayer tMap = null;
        try {
            // open the file
            InputStream is = this.getClass().getResourceAsStream(fpath);
            DataInputStream ds = new DataInputStream(is);
            try {
                // skip the descriptor
                ds.skipBytes(8);
                
                // read map width
                int mW = ds.readByte();
                
                // read map height
                int mH = ds.readByte();
                
                // read tile width
                int tW = ds.readByte();
                
                // read tile height
                int tH = ds.readByte();
                
                // create a new tiled layer
                tMap = new TiledLayer(mW, mH, ftiles, tW, tH);
                
                // loop through the map data
                for (int rCtr = 0; rCtr < mH; rCtr++){
                  for (int cCtr = 0; cCtr < mW; cCtr++){
                    // read a tile index 
                    byte nB = ds.readByte();  
                    
                    // if tile index is non-zero
                    // tile index 0 is usually a blank tile
                    if (nB > 0) {
                        //assign (tile index + 1) to the current cell
                        tMap.setCell(cCtr, rCtr, nB + 1);  
                    }
                  }  
                }

           } catch (Exception ex) {
               tMap = null;
               System.err.println("map loading error : " + ex.getMessage());
           }
           // close the file
           ds.close();
           ds = null;
           is = null;
        } catch (Exception ex) {
            tMap = null;
            System.err.println("map loading error : " + ex.getMessage());
        }
        
        // return the newly created map or null if loading failed
        return tMap;
    }    
    
    public void load(){
        try{
            // load the images here
            imgVan = Image.createImage("/images/van.png");
            imgTileset = Image.createImage("/images/tileset1.png");
        }catch(Exception ex){
            // exit the app if it fails to load the image
            isRunning = false;
            return;
        }
        
        // initialize the Sprite object
        Van = new Sprite(imgVan, 18, 18);
        
        // show the frame 1 - the second frame
        Van.setFrame(1);
        
        // move to 50, 50 (X, Y)
        Van.setPosition(16, 16);
        
        loadRoadMap();
        loadBlockMapFile();
        
    }
    
    public void unload(){
        // make sure the object gets destroyed
        blockMap = null;
        roadMap = null;
        Van = null;
        imgTileset = null;
        imgVan = null;
    }
    
    public void checkKeys(int iKey, long currTick){
        long elapsedTick = 0;
        //loop through the keys
        for (int i = 0; i < 5; i++){ 
            // by default, key not pressed by user
            isDown[i] = false;
            // is user pressing the key
            if ((iKey & keyValue[i]) != 0){ 
                elapsedTick = currTick - keyTick[i];
                //is it time to toggle key state?
                if (elapsedTick >= keyDelay){ 
                    // save the current time
                    keyTick[i] = currTick;  
                    // toggle the state to down or pressed
                    isDown[i] = true; 
                }
            }
        }
    }

    public void run() {
       int iKey = 0;
       int screenW = getWidth();
       int screenH = getHeight();
       long lCurrTick = 0; // current system time in milliseconds;
       
       load();
       g = getGraphics();
       while(isRunning){
           
           lCurrTick = System.currentTimeMillis();
           iKey = getKeyStates();
           
           checkKeys(iKey, lCurrTick);
           
           if (isDown[fireKey]){
               isRunning = false;    
           }
           
           // get the current position of the van
           int cx = Van.getX();
           int cy = Van.getY();
           
           // save the current position in temporary vars
           // so we can restore it when we hit a block
           int tx = cx;
           int ty = cy;
           
           if ((iKey & GameCanvas.UP_PRESSED) != 0){
               // show the van facing up
               Van.setFrame(0);
               
               // move the van upwards
               cy -= vanSpeed;
           } else if ((iKey & GameCanvas.DOWN_PRESSED) != 0){
               // show the van facing down
               Van.setFrame(1);
               
               // move the van downwards
               cy += vanSpeed;
           } else if ((iKey & GameCanvas.LEFT_PRESSED) != 0){
               // show the van facing left
               Van.setFrame(2);
               
               // move the van to the left
               cx -= vanSpeed;
           } else if ((iKey & GameCanvas.RIGHT_PRESSED) != 0){
               // show the van facing right
               Van.setFrame(3);
               
               // move the van to the right
               cx += vanSpeed;
           }
           
           // update the vans position
           Van.setPosition(cx, cy);
           
           //check if the van hits a block
           if (Van.collidesWith(blockMap, true)){
             //reset the van to the original position  
             Van.setPosition(tx, ty);
           }
           
           //restore the clipping rectangle to full screen
           g.setClip(0, 0, screenW, screenH);
           
           /* comment out or remove this code
            
           set drawing color to black
           g.setColor(0x000000);
            
           fill the screen with blackness
           g.fillRect(0, 0, screenW, screenH);

            */
           
           //draw the road
           roadMap.paint(g);
           
           //draw the blocks
           blockMap.paint(g);
           
           // draw the sprite
           Van.paint(g);
           
           flushGraphics();
           
           try{
               Thread.sleep(30);
           } catch (Exception ex){
               
           }
       }
       g = null;
       unload();
       fParent.destroyApp(false);
       fParent = null;
    }
}

Bạn cũng có thể tìm các phần mềm tạo tilemap trên mạng, thậm chí có thể save dữ liệu map vào file xml, vân vân...

Link vài phần mềm làm map:
Cuối cùng, bạn cũng có thể dùng chức năng Game Builder trong NetBeans 6 và gói Mobility.

Chúc các bạn thành công !!

Không có nhận xét nào:

Đăng nhận xét