Bitmap font giải quyết những vấn đề của các bạn gặp phải khi chỉ sử dụng phương thức drawString( ). Và dùng bitmap font cũng làm cho game của bạn trông đẹp hơn.
Trong bài học này chúng ta sẽ học về cách sử dụng bitmap font với sự giúp đỡ của phương thức setClip( ) và ép kiểu ( type casting ). Nếu bạn chưa biết setClip để làm gì thì hãy học qua bài này trước: Cắt hình ảnh hay hiển thị một phần của ảnh
Bạn có thể sử dụng project này dể thực hành:
Bảng mã ASCII
Như tôi đã đề cập, ảnh sẽ được dùng thay cho chữ và số. Thực ra, chúng là một chuỗi ảnh, mỗi phần tử chứa một chữ ( hoặc số ). Sau đây là một ví dụ cho các bạn:

Bitmap font sample zoomed in 2x.
Frame size: 9x10 pixels.
Click on the image to see what it actually looks like.
Vì đó là ảnh nên các bạn muốn vẽ đẹp xấu thế nào cũng được. Chỉ cần nhớ là font quá xấu thì bạn có thể làm người chơi chảy máu mắt...
... và xóa game của bạn luôn !!!
Các kí tự của bitmap font được sắp xếp theo bảng mã ASCII

Điều này làm chúng ta dễ dàng hơn trong việc sử dụng bitmap font bằng công thức sau:
positionX = ((int)theCharacter) * frameWidth;
Ép kiểu một char thành kiểu int cho kết quả là giá trị gốc của kí tự, tức giá trị thập phân trong bảng ASCII.
Bạn sẽ thấy các hình vuông có màu đen và xám trong ảnh ( ảnh project ). Đó là những kí tự không in ra. Bởi vì 32 kí tự đầu của bảng mã ASCII là không in ra được, có nghĩa là không thể xuất hiện trên màn hình. Hay như kí tự cuối - Del cũng không in ra được. Bởi vậy chỉ những kí tự có mã thập phân từ 31 đến 127 là vẽ lên màn hình được.
Vẽ Bitmap Font : lớp clsFont
Chúng ta tạo lớp mới tên là "clsFont". Khi xong các bạn sẽ thấy như sau:
package MyGame; public class clsFont { /** Creates a new instance of clsFont */ public clsFont() { } }
Tiếp đó ta khai báo các biến toàn cục dưới lời khai báo lớp:
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 :P
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 character
9};
// the bitmap font image
public Image imgFont;
Bấm ALT+Shift+F hoặc chọn "Fix Imports" từ menu "Source" để Netbean tự import các gói còn thiếu.
Biến charS để định nghĩa khoảng trống giữa các kí tự. Bạn có thể diều chỉnh nó nếu thấy các kí tự quá gần hay quá xa nhau. It can also be useful if you want to do a spring effect animation and make the characters bounce sideways.
Hai biến screenW và screenH định nghĩa khoảng màn hình lớn nhất chúng ta có thể vẽ lên. Chúng cũng chắc chắn rằng các khung cắt luôn rơi vào vùng đã định. Các bạn sẽ nhận ra điều này sau.
Tiếp theo chúng ta có biến useDefault. Điều này là bắt buộc và chúng ta sẽ dùng nó để báo hiệu khi hình ảnh không load được. Các bạn sẽ có thông tin về vấn đề này sau.
Biến charH lưu trữ chiều cao cực đại của các kí tự và dùng để điều chỉnh chiều cao khung cắt.
Mảng số nguyên charW[ ] lưu trữ độ rộng được tính toán trước của mỗi kí tự. Chúng ta dùng nó để tính toán vị trí chính xác các kí tự từ chuỗi đã cho và bao nhiêu không gian mỗi kí tự phải chiếm. Bằng cách này, chuỗi vẽ ra trông sẽ tự nhiên hơn. Việc này cũng giúp tiết kiệm không gian màn hình. Không giống như các phông chữ theo phong cách đơn khối ( mono block ) nơi mà mỗi kí tự sử dụng cùng một chiều rộng ngay cả khi có những kí tự to và kí tự nhỏ hơn. Một điều nữa cần chú ý về giá trị trong charW[ ] là chúng đã bao gồm khoảng cách giữa các kí tự rồi. Nhưng bạn vẫn có thể dùng biến charS để điều chỉnh khoảng cách nếu cần.
Biến cuối cùng imgFont sẽ lưu giữ ảnh bitmap font.
Bây giờ ta thêm phương thức load( ) để load ảnh chứa font và phương thức unload( ) để dọn dẹp khi không dùng đến class này nữa.
/** Creates a new instance of clsFont */
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 get's destroyed
imgFont = null;
}
Phương thức load( ) lấy tham số imagePath - đường dẫn đến bitmap font chúng ta cần load. Nếu load thất bại thì useDefault được set giá trị true. Bạn có thể in giá trị này ra màn hình để biết botmap font có được load thành công hay không. Phương thức load () cũng trả về giá trị nghịch đảo của useDefault, do đó bạn có thể sử dụng phương pháp này để vừa load vừa kiểm tra xem bitmap font có được load hay không:
if (!myFont.load("/images/fonts.png")){
/*
...do something to handle the error
when the image fails to load...
...
...
*/
}
Tiếp theo chúng ta thêm phương thức drawChar( ) dùng để vẽ một kí tự lên màn hình. Thêm phương thức này vào dưới phương thức unload( ) nhé.
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); }
...phương thức drawChar( ) có các tham số sau:
- Graphics g - đối tượng dùng để vẽ kí tự
- int cIndex - giá trị kí tự được vẽ ra
- int x - tọa độ x khung cắt dành cho kí tự
- int y - tọa độ y khung cắt dành cho kí tự
- int w - chiều rộng kí tự và khung cắt
- int h - chiều cao kí tự và khung cắt
Phương thức drawChar( ) kiểm tra xem kí tự sắp vẽ có thể in ra hay không, thông qua giá trị cIndex. Sau đó nó tính toán vị trí của kí tự trong ảnh, lưu trữ lại để dùng. Phương thức cũng reset khung cắt trở lại toàn màn hình và điều chỉnh chính xác vị trí khung cắt đến nơi kí tự phải được vẽ ra và làm phù hợp với kích thước của nó bằng phương thức clipRect( ). Sau đó, nó sẽ vẽ kí tự lên màn hình trong khung cắt.
Hãy thêm phương thức cuối cùng cho lớp clsFont, phương thức drawString( ). Đây là phương thức dùng để vẽ lời thoại, điểm số, vân vân... Thêm vào dưới phương thức drawChar()
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); } }
Các tham số của phương thức drawString()
- Graphics g - đối tượng Graphics để vẽ chuỗi
- String sTxt - chuỗi cần vẽ
- int x - tọa độ x của chuỗi
- int y - tọa độ y của chuỗi
Bây giờ bạn sẽ biết useDefault dùng để làm gì. Nếu nó được set giá trị true, có nghĩa là bitmap font không được load, thì phương thức drawString của đối tượng Graphics g sẽ được dùng để vẽ chuỗi lên thay vì dùng bitmap font.
Phương thức drawString( ) lặp qua từng kí tự trong chuỗi và lấy thứ tự của mỗi kí tự. Số thứ tự này được dùng để lấy chiều rộng của từng kí tự trong bảng charW[ ]. Nó cũng thông qua phương thức drawChar( ) để xác định chiều rộng của khung cắt. Sau khi kí tự vừa được vẽ ra, độ rộng được cộng thêm vào tọa độ vẽ hiện thời và tiếp tục với kí tự kế tiếp. Nếu bạn đặt giá trị khác 0 vào charS, nó vẫn sẽ được cộng vào.
Và sau đây là lớp clsFont hoàn chỉnh cho các bạn đối chiếu:
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; /** Creates a new instance of clsFont */ 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 get's 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); } } }
code không chạy được a ơi, (x-cx) ra âm, , a cho nguyên cái source được không ?
Trả lờiXóa