Thứ Sáu, 31 tháng 8, 2012

Sử dụng Custom Fonts hay Bitmap Fonts trong MIDP 2.0 Part II


Đến phần: 1 | 2

Trong phần trước, chúng ta đã tạo lớp clsFont để vẽ chữ bằng bitmap font. Bài học hôm nay sẽ sử dụng class này để xuất chữ lên màn hình.

Sử dụng Bitmap Font

Hãy mở code lớp clsCanvas để bắt đầu. Chúng ta sẽ thêm biến toàn cục myFont làm đối tượng lớp clsFont.


private midMain fParent;

private clsFont myFont;
    /** Creates a new instance of clsCanvas */
    public clsCanvas(midMain m) {

Nếu bạn mở folder images ra sẽ thấy ảnh fonts.png trong đó. Đấy chính là font chúng ta sử dụng trong phương thức clsFont.load( ). Thêm 2 dòng sau vào cuối phương thức load( ) của lớp clsCanvas.



        }
        
        myFont = new clsFont();
        myFont.load("/images/fonts.png");
    }

Thêm lời gọi phương thức unload của clsFont vào cuối phương thức unload( ) của clsCanvas



    public void unload(){
        // make sure the object get's destroyed

        myFont.unload();
        myFont = null;
    }

Cuối cùng ta dùng phương thức drawString( ) của clsFont để vẽ chữ lên màn hình. Thêm các dòng sau trước lời gọi phương thức flushGraphics( ) trong vòng lặp chính:



           g.fillRect(0, 0, screenW, screenH);
           
           myFont.drawString(g, "Hello Neo...", 10, 50);
           myFont.drawString(g, "1234567890", 10, 70);
           myFont.drawString(g, "ABCDEFG abcdefg", 10, 90);           
           flushGraphics();

Sau đó chúng ta test những gì vừa làm được:


Bitmap Font on WTK 2.5.1

Sau đây là code hoàn chỉnh lớp clsCanvas:

package MyGame;

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

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 clsFont myFont;
    /** 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 load(){
        try{
            // load the images here
            
        }catch(Exception ex){
            // exit the app if it fails to load the image
            isRunning = false;
            return;
        }
        
        myFont = new clsFont();
        myFont.load("/images/fonts.png");
    }
    
    public void unload(){
        // make sure the object gets destroyed

        myFont.unload();
        myFont = 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;    
           }
           
           //restore the clipping rectangle to full screen
           g.setClip(0, 0, screenW, screenH);
           //set drawing color to black
           g.setColor(0x000000);
           //fill the screen with blackness
           g.fillRect(0, 0, screenW, screenH);
           
           myFont.drawString(g, "Hello Neo...", 10, 50);
           myFont.drawString(g, "1234567890", 10, 70);
           myFont.drawString(g, "ABCDEFG abcdefg", 10, 90);           
           flushGraphics();
           
           try{
               Thread.sleep(30);
           } catch (Exception ex){
               
           }
       }
       g = null;
       unload();
       fParent.destroyApp(false);
       fParent = null;
    }
}


Xem xét vấn đề
Bạn cũng có thể xem xét thêm phương thức wrappers trong lớp clsFont để vẽ kiểu int như sau:



    public void drawInt(Graphics g, int num, int x, int y){
        drawString(g, Integer.toString(num), x, y);
    }

    public void drawLong(Graphics g, long num, int x, int y){
        drawString(g, Long.toString(num), x, y);
    }

Một biến thể của phương thức drawString( ) vẽ ra chuỗi với lề bên phải có thể được tạo một cách dễ dàng với đoạn code sau:



    //draws string from right to left starting at x,y
    public void drawStringRev(Graphics g, String sTxt, int x, int y){
        // get the strings length
        int len = sTxt.length();

        // set the starting position
        int cx = x;
        
        // if nothing to draw return
        if (len == 0) {
            return;
        }
        
        // our fail-safe
        if (useDefault){
            g.drawString(sTxt, x, y, Graphics.TOP | Graphics.RIGHT);
            return;
        }

        // loop through all the characters in the string      
        for (int i = (len - 1); i >= 0; i--){

           // get current character 
           char c = sTxt.charAt(i);

           // get ordinal value or ASCII equivalent
           int cIndex = (int)c;

           // lookup the width of the character
           int w = charW[cIndex];

           // go to the next drawing position
           cx -= (w + charS);

           // draw the character
           drawChar(g, cIndex, cx, y, w, charH);
        }
    }

Với phương thức vẽ kiểu int:

    public void drawIntRev(Graphics g, int num, int x, int y){
        drawStringRev(g, Integer.toString(num), x, y);
    }

    public void drawLongRev(Graphics g, long num, int x, int y){
        drawStringRev(g, Long.toString(num), x, y);
    }

Những phương thức vừa thêm có thể dùng để vẽ điểm số và chữ số căn lề bên phải.

Bitmap Font với chức năng Word Wrap

Sau đây là các phương thức bổ sung để bạn có thể dùng chức năng word wrap với bitmap font


    // space between lines in pixels
    public int lineS = 2; 
    
    // draws words that wrap between x and x1
    public void drawStringWrap(Graphics g, String s, int x, int y, int x1){
        int len = s.length();
        
        // current x
        int tx = x;
        
        // current y
        int ty = y;
        
        /*
           word buffer contents width -
           I just thought it would be faster than 
           calling the String.length() method
        */
        int ww = 0; 
        
        // word buffer
        String sWord = "";
        
        for (int i = 0; i < len; i++){
            char c = s.charAt(i);
            int cIndex = (int)c;
            int cw = charW[cIndex];
            
            if ((cIndex > 32) && (cIndex < 127)){
              //if not a space and the character is printable 
                
              //add the character to the buffer
              sWord += String.valueOf(c);
              
              //compute the length of the current word
              ww += cw;
            } else {
               //if space or non-printable character
               
               // check if there is a word in the buffer 
               if (ww > 0) {
                   
                   //check if the word goes past the right margin
                   if ((tx + ww) > x1){
                       // carrage return
                       tx = x;
                       
                       // line feed
                       ty += (charH + lineS);
                   }
                   
                   // draw the contents of the word buffer
                   drawString(g, sWord, tx, ty);
               }
               
               //move to the next position
               tx += (ww + cw); 
               // clear the word buffer
               sWord = "";
               // word buffer width to zero
               ww = 0;
            }
        }

        // if there is a word remaining in the buffer then draw it
        if (ww > 0) {
            if ((tx + ww) > x1){
                tx = x;
                ty += (charH + lineS);
            }
            drawString(g, sWord, tx, ty);
        }
    }

Phương thức drawStringWrap( ) gồm các tham số sau
  • Graphics g - đối tượng Graphics dùng vẽ font
  • String s - chuỗi cần vẽ
  • int x - lề trái và tọa độ x bắt đầu vẽ
  • int y - tọa độ y dòng đầu tiên
  • int x1 - lề phải để chữ tự động xuống dòng
Phương thức drawStringWrap( ) sử dụng phương thức drawString( ) để vẽ các từ. Phương thức này xem "các kí tự không hiển thị được và dấu cách" là một "dấu phân cách giữa các từ". Bạn có thể thay đổi khoảng cách các dòng với nhau thông qua giá trị biến lineS.

Để thử nghiệm, bạn thêm đoạn code sau vào trước lời gọi flushGraphics( )


           myFont.drawString(g, "ABCDEFG abcdefg", 10, 90);

           myFont.drawStringWrap(g, "blah blah blah blah blah blah " +
                   "blah blah blah blah blah blah blah blah blah " +
                   "blah blah blah blah blah blah blah blah blah " +
                   "blah blah blah blah blah blah blah blah blah " +
                   "blah blah blah blah blah blah blah blah blah " +
                   "blah ", 10, 100, 166);
           flushGraphics();

Bạn sẽ thấy kết quả thế này


Bitmap Font With Wrapping on WTK 2.5.1

Sau đây là code hoàn chỉnh lớp clsFont sau khi thêm các phương thức mới

package MyGame;

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

public class clsFont {
    // additional space between characters
    public int charS = 0;
    
    // max clipping area
    public int screenW = 176;
    public int screenH = 208;
    
    // flag: set to true to use the Graphics.drawString() method
    // this is just used as a fail-safe
    public boolean useDefault = false;
    
    // height of characters
    public int charH = 10;
    
    // lookup table for character widths
    public int[] charW = {
    // first 32 characters    
    9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 
    9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 
    9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 
    9, 9,
    // space
    9,
    // everything else XD
    3, 5, 8, 8, 7, 8, 3, 5, 5, 6, 
    7, 3, 7, 3, 9, 6, 4, 6, 6, 6, 
    6, 6, 6, 6, 6, 3, 3, 6, 6, 6, 
    6, 9, 6, 6, 6, 6, 6, 6, 6, 6, 
    3, 6, 6, 6, 9, 6, 6, 6, 6, 6, 
    6, 7, 6, 6, 9, 6, 6, 6, 5, 9, 
    5, 4, 6, 4, 6, 6, 6, 6, 6, 6, 
    6, 6, 3, 4, 6, 3, 9, 6, 6, 6, 
    6, 6, 6, 6, 6, 6, 9, 6, 6, 6, 
    5, 3, 5, 4,
    // delete
    9};
    
    // the bitmap font image
    private Image imgFont;
    
    public clsFont() {
    }
    
    public boolean load(String imagePath){
        useDefault = false;
        try{
            // load the bitmap font
            if (imgFont != null){
                imgFont = null;
            }
            imgFont = Image.createImage(imagePath);
        } catch (Exception ex){
            // oohh we got an error then use the fail-safe
            useDefault = true;
        }
        return (!useDefault);
    }
    
    public void unload(){
        // make sure the object gets destroyed
        imgFont = null;
    }
    
    public void drawChar(Graphics g, int cIndex, int x, int y, int w, int h){
         // non printable characters don't need to be drawn
        if (cIndex < 33){
            return;
        }

        // neither does the delete character 
        if (cIndex > 126){
            return;
        }

        // get the characters position
        int cx = cIndex * 9;

        // reset the clipping rectangle
        g.setClip(0, 0, screenW, screenH);

        // resize and reposition the clipping rectangle
        // to where the character must be drawn
        g.clipRect(x, y, w, h);

        // draw the character inside the clipping rectangle
        g.drawImage(imgFont, x - cx, y, Graphics.TOP | Graphics.LEFT);
    }
    
    public void drawString(Graphics g, String sTxt, int x, int y){
        // get the strings length
        int len = sTxt.length();

        // set the starting position
        int cx = x;
        
        // if nothing to draw return
        if (len == 0) {
            return;
        }
        
        // our fail-safe
        if (useDefault){
            g.drawString(sTxt, x, y, Graphics.TOP | Graphics.LEFT);
            return;
        }

        // loop through all the characters in the string      
        for (int i = 0; i < len; i++){

           // get current character 
           char c = sTxt.charAt(i);

           // get ordinal value or ASCII equivalent
           int cIndex = (int)c;

           // lookup the width of the character
           int w = charW[cIndex];

           // draw the character
           drawChar(g, cIndex, cx, y, w, charH);

           // go to the next drawing position
           cx += (w + charS);
        }
    }
    
    // extended methods ***************************************

    public void drawInt(Graphics g, int num, int x, int y){
        drawString(g, Integer.toString(num), x, y);
    }

    public void drawLong(Graphics g, long num, int x, int y){
        drawString(g, Long.toString(num), x, y);
    }
    
    // Right align methods ****************************************
    
    //draws string from right to left starting at x,y
    public void drawStringRev(Graphics g, String sTxt, int x, int y){
        // get the strings length
        int len = sTxt.length();

        // set the starting position
        int cx = x;
        
        // if nothing to draw return
        if (len == 0) {
            return;
        }
        
        // our fail-safe
        if (useDefault){
            g.drawString(sTxt, x, y, Graphics.TOP | Graphics.RIGHT);
            return;
        }

        // loop through all the characters in the string      
        for (int i = (len - 1); i >= 0; i--){

           // get current character 
           char c = sTxt.charAt(i);

           // get ordinal value or ASCII equivalent
           int cIndex = (int)c;

           // lookup the width of the character
           int w = charW[cIndex];

           // go to the next drawing position
           cx -= (w + charS);

           // draw the character
           drawChar(g, cIndex, cx, y, w, charH);

        }
    }

    public void drawIntRev(Graphics g, int num, int x, int y){
        drawString(g, Integer.toString(num), x, y);
    }

    public void drawLongRev(Graphics g, long num, int x, int y){
        drawString(g, Long.toString(num), x, y);
    }

    // Word wrap method ****************************************
    
    // space between lines in pixels
    public int lineS = 2; 
    
    // draws words that wrap between x and x1
    public void drawStringWrap(Graphics g, String s, int x, int y, int x1){
        int len = s.length();
        
        // current x
        int tx = x;
        
        // current y
        int ty = y;
        
        /*
           word buffer contents width -
           I just thought it would be faster than 
           calling the String.length() method
        */
        int ww = 0; 
        
        // word buffer
        String sWord = "";
        
        for (int i = 0; i < len; i++){
            char c = s.charAt(i);
            int cIndex = (int)c;
            int cw = charW[cIndex];
            
            if ((cIndex > 32) && (cIndex < 127)){
              //if not a space and the character is printable 
                
              //add the character to the buffer
              sWord += String.valueOf(c);
              
              //compute the length of the current word
              ww += cw;
            } else {
               //if space or non-printable character
               
               // check if there is a word in the buffer 
               if (ww > 0) {
                   
                   //check if it goes past the right margin
                   if ((tx + ww) > x1){
                       // carrage return
                       tx = x;
                       
                       // line feed
                       ty += (charH + lineS);
                   }
                   
                   // draw the contents of the word buffer
                   drawString(g, sWord, tx, ty);
               }
               
               //move to the next position
               tx += (ww + cw); 
               
               // clear the word buffer
               sWord = "";
               
               // word buffer width to zero
               ww = 0;
            }
        }
        
        // if there is a word remaining in the buffer then draw it
        if (ww > 0) {
            if ((tx + ww) > x1){
                tx = x;
                ty += (charH + lineS);
            }
            drawString(g, sWord, tx, ty);
        }
    }
}

Còn rất nhiều thứ có thể làm để thêm chức năng cho clsFont. Ví dụ như căn chỉnh chữ, tối ưu các code trên hay thêm các font chữ khác và viết code cho người chơi lựa chọn loại font họ thích...

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

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

Đăng nhận xét