Chủ Nhật, 29 tháng 7, 2012

Basic MIDP 2.0 Game Template Part 1

Bài viết sau đây được dịch sang tiếng Việt bởi Red_Scorpion, bản quyền Devlin (http://devlinslab.blogspot.com/2007/10/basic-game-template-part-2.html)

Đến phần: 1 | 2

Tạo một GameCanvas

Chúng ta phải tạo một canvas kế thừa lớp GameCanvas. Canvas ở đây chính là nơi những ảnh, sprite, map, điểm số... được vẽ ra trên màn hình chiếc điện thoại. Nó cũng cho chúng ta biết phím nào được nhấn và chúng ta có thể thao tác với chúng.
Chọn NewFile từ menu File. Từ danh sách đưa ra, chọn Java Class sau đó bấm Next. Hoặc là bạn cũng có thể bấm chuột phải vào package (gói) và chọn New, rồi chọn Java Class. Nhớ không đánh dấu ô create Hello MIDlet.

Đánh "clsCanvas" vào ô Class Name rồi bấm Next. Bạn sẽ thấy code tự sinh ra:
New Java Class


The empty project


*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package pkgGame;

/**
 *
 * @author Devlin
 */
public class clsCanvas {

}

Bây giờ chúng ta sẽ chuyển nó thành một class kế thừa lớp GameCanvas. Chỉnh đoạn code sau...

public class clsCanvas {

...thành thế này:

public class clsCanvas extends GameCanvas implements Runnable {

Code của bạn sẽ trông như sau (ở đây tôi đã xóa hết các chú thích tự sinh ra cho dễ nhìn):
package pkgGame;

import javax.microedition.lcdui.game.GameCanvas;

public class clsCanvas extends GameCanvas implements Runnable {

}

Bạn sẽ thấy các dòng đỏ báo lỗi rất ngứa mắt. Hãy thêm hàm constructor vào code
...code sẽ trở thành như sau:

package MyGame;

import javax.microedition.lcdui.game.GameCanvas;

public class clsCanvas extends GameCanvas implements Runnable {
  
   public clsCanvas() {
       super(false);
   }

}

lớp clsCanvas kế thừa lớp GameCanvas và cũng thừa kế luôn constructor của nó. Đoạn code trong constructor của chúng ta bỏ qua giá trị "false" của lớp cha. Kế tiếp, giao diện Runnable buộc chúng ta phải thêm phương thức run( )

public class clsCanvas extends GameCanvas implements Runnable {

    public clsCanvas() {
        super(false);
    }

    public void run() {

    }

}

Thế là hết báo lỗi.

Lớp cha GameCanvas yêu cầu chúng ta sử dụng constructor của nó, có một  biến boolean là suppressKeyEvents. Nó sẽ bỏ qua các event của phím như keyPressed, keyRepeated và keyRealeased và làm nhẹ code đi. Vậy thì, làm thế nào cho người dùng nhấn phím? Chúng ta sẽ sử dụng hàm getKeyStates( ) để phát hiện phím vừa được nhấn. Chúng ta sẽ đề cập vấn đề này sau.

Lớp canvas của chúng ta cũng sẽ sử dụng giao diện Runnable cho phép chạy game với nhiều luồng khác nhau. Muốn thực hiện diều đó chúng ta cần xây dựng hàm run( ). Hàm này sẽ chứa vòng lặp game chính và các code liên quan.

Khai báo biến isRunning với giá trị ban đầu là true

public class clsCanvas extends GameCanvas implements Runnable {
private boolean isRunning = true;

Game là một vòng lặp lớn chạy liên tục. Vòng lặp đó chính là run( ) và sử dụng giá trị biến isRunning

    public void run() {
       while(isRunning){

       }
    }
Một chức năng của GameCanvas là bộ đệm off-screen vẽ những thứ ta muốn trước rồi hiển thị lên màn hình bằng cách gọi phương thức flushGraphics( ). Bằng cách vẽ này game sẽ mượt và nhanh hơn. Vì vậy cuối mỗi vòng lặp dùng để vẽ ta gọi phương thức này.

Với biến isRunning set giá trị true, vòng lặp while sẽ lặp vô tận. Chúng ta phải thêm vào một cách dừng vòng lặp để kết thúc trò chơi. Hãy viết chương trình cho phép kết thúc trò chơi khi người dùng nhấn phím OK hay phím 5. Khi nhấn OK, biến isRunning sẽ được set giá trị false, và khi đó vòng lặp while sẽ ngừng ngay lập tức. Đến đây thì phương thức getKeyStates() giới thiệu ở trên sẽ được dùng đến. Sửa lại hàm run( ) như sau:

public void run() {
        while(isRunning){
            // get keys pressed
            int iKey = getKeyStates();
            // check if FIRE or 5 is pressed
            if ((iKey & FIRE_PRESSED) != 0){
                // signal an exit
                isRunning = false;
            }


            try{
                // make this thread sleep for 30 ms
                Thread.sleep(30);
            } catch(Exception e){

            }
        }
    }

Chúng ta cho luồng này nghỉ 30ms trước khi lặp bằng Thread.sleep(30). Nếu không, game sẽ không nhận phím từ người chơi và tiêu tốn điện năng. Giá trị 30 càng nhỏ thì game càng nhanh và ngược lại.

Có hai thứ cần cho việc tạo một thread (luồng) mới. Bạn cần một thực thể của lớp Thread và một đối tượng có giao diên Runnable. Chúng ta đã có đối tượng kế thừa giao diện Runnable vì vậy cần phải tạo thực thể của lớp Thread. Chúng ta sẽ thêm phương thức start( ) sau ngay phía trên phương thức run( )

    public void start(){
        // creat a new instance of the Thread class
        Thread thread = new Thread(this);
        // launch the thread
        thread.start();
    }

    public void run() {


Khai báo một biến toàn cục g có kiểu Graphics:

private boolean isRunning = true;    
private Graphics g;

Thêm đoạn mã sau vào hàm run(), ngay trước vòng lặp while:


       g = getGraphics();
       while(isRunning){

Java có một bộ dọn rác, sẽ giúp chúng ta khi chúng ta gán giá trị null cho các biến không còn cần đến nữa. Vì vậy, khi dùng xong đối tượng Graphics g, chúng ta sẽ set g = null.
Thêm vào vài đoạn mã thực hiện việc vẽ để thấy thành quả ta làm được.
    public void run() {
        g = getGraphics();
        while(isRunning){
            // get key state
            int iKey = getKeyStates();
            // check if FIRE or 5 is pressed
            if ((iKey & FIRE_PRESSED) != 0){
                // signal an exit
                isRunning = false;
            }

            //set drawing color to black
            g.setColor(0x000000);
            //fill the whole screen with black
            g.fillRect(0, 0, getWidth(), getHeight());
            // set drawing color to white
            g.setColor(0xffffff);
            //display a string
            g.drawString("Basic Java ME Game Template", 2, 2, Graphics.TOP | Graphics.LEFT);
            //display the key states in white
            g.drawString(Integer.toString(iKey), 2, 22, Graphics.TOP | Graphics.LEFT);
            
     // show everything we drew on the screen
            flushGraphics();

            try{
                // make this thread sleep for 30 ms
                Thread.sleep(30);
            } catch(Exception e){

            }
        }
        // mark as garbage
        g = null;
    }

Một chức năng của lớp GameCanvas là bộ đệm màn hình, nơi bạn vẽ mọi thứ trước khi hiển thị. Những gì bạn vẽ sẽ thực sự hiển thị bằng cách gọi phương thức flushGraphics( ). Việc vẽ trên bộ đệm sẽ giúp game nhanh và mượt hơn. Phương thức này được gọi ở cuối mỗi vòng lặp.

Đối tượng Graphics có các phương thức dùng để vẽ. Hãy chú ý, phương thức setColor( ) sẽ làm màu vẽ thay đổi cho đến khi nó được gọi để set màu khác.

Nhân tiện, bạn có thể tham khảo cách sử dụng luồng ở đây:  Using Threads in J2ME Applications 

Trước khi vào phần kết của bài này, chúng ta phải có một cách nào đó để clsCanvas nói cho MIDlet biết là game đã kết thúc và đóng MIDlet lại. Đầu tiên, tạo biến toàn cục p với kiểu dữ liệu là class MIDlet (class MIDlet của chúng ta sẽ là midMain) để lớp clsCanvas có thể gọi được các phương thức có trong class đó.
public class clsCanvas extends GameCanvas implements Runnable {
    private boolean isRunning = true;
    private Graphics g;
    private midMain p;


Sau đó, sửa constructor của clsCanvas như sau
    public clsCanvas(midMain parent) {
        super(false);
        p = parent;
    }

Cuối cùng ta gọi phương thức destroyedApp( ) của MIDlet khi vòng lặp chính kết thúc và đóng MIDlet lại.

        ...
        ...
        // mark as garbage
        g = null;
        // close the MIDlet
        p.destroyApp(true);
        p = null;
    }

Bài kế tiếpDisplaying the GameCanvas