Đỉnh NGUYỄN

life's a journey not a destination

Programming 2D Games in J2ME

7 Comments


Download Source – 3.73 Mb

Giới thiệu

J2ME là môi trường tuyệt vời để lập trình game cho thiết bị cầm tay. Với kiến thức cơ bản về Java, cài đặt Eclipse (thêm MTJ) hoặc Netbeans (thêm Mobility pack) và Java Wireless Toolkit hoặc Java 2 ME SDK 3.0, là đã có 1 môi trường phát triển game chạy trên thiết bị di động.

Bài này dùng các lớp trong tập API dùng lập trình game trong gói javax.microedition.lcdui.game

Nền tảng

Giả định bạn có kiến thức cơ bản về Java, quen với Netbeans hoặc Eclipse. Làm game cũng cần yêu cầu nên có kiến thức vững về định luật hấp dẫn của Newton, sự chuyển động, sự va chạm, ma sát, đồ họa và âm thanh,…

Dùng mã lệnh

MainMidlet

MainMidlet kế thừa từ lớp trừu tượng Midlet, bạn có thể tìm thấy nó trong gói javax.microedition.midlet. Midlet bắt buộc phải ghi đè (override) 3 phương thức

– startApp() – được gọi để khởi động ứng dụng.

– pauseApp() – được gọi để tạm thời ngừng ứng dụng, ví dụ khi đang nhận 1 cuộc gọi đến. Ứng dụng sẽ ngừng các hoạt động và trả tài nguyên mà nó không cần. Ứng dụng có thể quay lại trạng thái hoạt động (resume) bằng cách gọi resumeMIDlet()

– destroyApp(boolean unconditional) – được gọi khi thoát ứng dụng. Trong trình tự thoát ứng dụng, MIDlet sẽ gọi notifyDestroyed()

Những phương thức này tự động được tạo ra khi khi bạn tạo 1 Visual Midlet trong Netbeans, hoặc khi tạo 1 project và thêm 1 Midlet trong Eclipse)

Ở đây chúng ta chỉ cần cài đặt startApp() để tạo thể hiện (instance) GameCanvas và thêm CommandListener để thoát Midlet. Đây chưa phải cách tốt nhất, nhưng ở bước này, chúng ta chỉ muốn ứng dụng chạy. Đối tượng được hiển thị ra màn hình được thiết lập ở cuối phương thức, phương thức setCurrent với Displayable làm đối số.

public class MainMidlet extends MIDlet implements CommandListener {
    private SSGameCanvas gameCanvas;
    private Command exitCommand;

    public void startApp() {
        try {
            //create new game thread            
            gameCanvas = new SSGameCanvas();
            gameCanvas.start(); // start game thread            
            exitCommand = new Command("Exit", Command.EXIT, 1);
            gameCanvas.addCommand(exitCommand);
            gameCanvas.setCommandListener(this);
            Display.getDisplay(this).setCurrent(gameCanvas);
        } catch (java.io.IOException e) {
            e.printStackTrace();
        }
    }

    public void pauseApp() {
    }

    public void destroyApp(boolean unconditional) {
    }

    public void commandAction(Command command, Displayable displayable) {
        if (command == exitCommand) {
            destroyApp(true);
            notifyDestroyed();
        }
    }
}

Là thành phần giao diện cấp thấp, khi kết hợp với Graphics, GameCanvas cung cấp cách thức linh động để tạo màn hình game. Với Graphics, vẽ đồ họa 2D, gồm vẽ hình, văn bản, hình ảnh. GameCanvas là sự mở rộng của Canvas có sẵn với nhiều phương thức đồ họa được hỗ trợ

Dùng GameCanvas, đồ họa có thể lưu đệm trong bộ nhớ, khi cần có thể gọi flushGraphics(), các đối tượng đồ họa sẽ nhanh chóng hiển thị ra màn hình.

GameCanvas cũng cho phép nhận các sự kiện phím và xác định trạng thái với getKeyState(). Tuy nhiên, GameManager giúp quản lý điều này tốt hơn nhiều

Hệ thống tọa đồ đồ hoạt trong Java Game bắt đầu từ góc trái bên trên của màn hình như sau:

Trong phương thức render, những gì trên màn hình sẽ bị xóa, và đối tượng đồ họa sẽ được vẽ lại bằng cách gọi phương thức paint của lớp GameManager

public void render(Graphics g){
    
    ...
    // Clear the Canvas.        
    g.setColor(0,0,50);
    g.fillRect(0,0,WIDTH-1,HEIGHT-1);
    
    ...
    gameManager.paint(g);
}

Trong ví dụ này, SSGameCanvas cài đặt giao diện Runable, vì thế phương thức run sẽ giúp lặp lại quá trình này trong suốt quá trình ứng dụng game tồn tại.

Sự lặp lại trong Game

public void run() {
    while(running) {
        // draw graphics
        render(getGraphics());
        // advance to the next tick
        advance(tick++);
        // display
        flushGraphics();
        try {
                Thread.sleep(mDelay);
        }
        catch(InterruptedException ie) { }
    }
}

Thời gian trong game được điều khiển bởi biến kiểu interger tên là tick. tick làm đơn giản hóa vấn đề thời gian trong game, như là tốc độ tàu vũ trụ, bao lâu ngôn sao sẽ sáng lên, bao lâu sẽ sang màn chơi kết tiếp. Nếu cần phải xác định thời gian trong game, GameCanvas sẽ phải cài đặt giao diện (interface) Runnable, tick nghĩa là mDelay + thời gian hoàn tất 1 chù kỳ game (milli giây). Chúng ta có lẽ chỉ cần 24-30 fps, vì thế chúng ta giới hạn mDelay phù hợp với những chuyển động. Với mỗi chu kỳ của game, chúng ta gọi các phương thức nâng cao của GameManager, một mở rộng của LayerManager để kiểm tra nhập liệu, sự va chạm và vẽ lại đồ họa game.

public void advance(int ticks) {      
    // advance to next game canvas       
   
gameManager.advance(ticks);       
    this.paint(getGraphics());   
}

tick có 1 giới hạn: nó giới hạn thời gian chơi game bời vì giới hạn của số integer, nó vào khoản 590 giờ trên hệ thống 32 bit.

Sprite

Sprite hoạt động như các tác nhân trong game. Nó có thể xem như nhân vận Mario, con vịt, và các khẩu súng trong game Mario hoặc tàu không gian trong game Chiến tranh giữa các vì sao. Các yếu tố trực quan cơ bản sẽ được kéo dài, hiển thị liên tục với nhiều khung hình trên giây. Tất cả các frame phải cùng width và height

public SpaceShip(Image image, int w, int h) throws java.io.IOException {
    super(image,w ,h);
    WIDTH = w;
    HEIGHT= h;
    setFrameSequence(SEQUENCE);
    defineReferencePixel(WIDTH/2, HEIGHT/2);
    setTransform(this.TRANS_MIRROR_ROT270);
}

Để khởi động tàu không gian (Sprite), gọi hàm dựng (constructor) từ lớp Sprite: super(image, w, h); với w (width) và h (height) của mỗi frame. Hình ảnh gồm 8 frame, vì thế sẽ lập 1 chỗi frame {0, 1, 2, 3, 4, 5, 6, 7} dùng setFrameSequence

Tiếp, chúng ta gọi defineReferencePixel để thiết lập tọa độ giữa frame. Tọa độ tính bằng pixel này sẽ được dùng định vị trí con tàu trên màn hình. Và cuối cùng, xao frame bởi phương thức setTransform

public void advance(int ticks) {
    if (ticks%RATE == 0)
        nextFrame();
}

Phương thức advance sẽ thay đổi frame của con tàu và tạo chuyển động RATE. Bằng cách gọi nextFrame() liên tục, màn hình sẽ hiển thị frame từ 0 đến 7 và sao đó quay trở lại 0: 0, 1, 2,…, 7, 0, 1, 2…Các phương thức moveLeft(), moveRight(), moveUp(), moveDown thay đổi vị trí con tàu trên màn hình phụ thuộc vào speedX và speedY

public void moveLeft () {
    if (this.getRefPixelX() > 0)
        this.move(-speedX, 0);
}

public void moveRight (int m) {
    if (this.getRefPixelX() < m)
        this.move(speedX, 0);
}

public void moveUp () {
    if (this.getRefPixelY() > 0)
        this.move(0, -speedY);
} 

public void moveDown (int m) {
    if (this.getRefPixelY() < m)
        this.move(0, speedY);
}

Khi con tàu được điều khiển nhả đạn, chúng ta kiểm tra nòng súng đã nguội bớt chưa bằng cách kiểm tra thời điểm bắn và thời điểm loạt bắn trước.

public Bullet fire (int ticks) {
    if (ticks- fireTick > SHOOT_RATE) {
        fireTick = ticks;
        bullet.setSpeed(BULLET_SPEED);
        bullet.shot(this.getRefPixelX(), this.getRefPixelY()+HEIGHT/2);
        return bullet;
    }
    else
    return null;
}

Để kiểm tra sự va chạm giữa các sprite, hình ảnh hoặc TitledLayer (sẽ được đề cập sau), dùng phương thức collidesWidth. Dùng GameManager để kiểm tra sự va chạm giữa con tàu và các hành tinh hoặc vì sao để giảm bớt HP của tàu, kiểm tra sự va chạm giữa đạn và các hành tinh để gia tăng điểm và hủy cả 2: viên đạn lẫn hành tinh.

private Image shipImage;
private static final String SHIP_IMAGE    = "/resource/blue_ship.png";
private static final int    SHIP_WIDTH    = 40;
private static final int    SHIP_HEIGHT   = 33;
shipImage = Image.createImage( SHIP_IMAGE );
// create space ship        
ship = new SpaceShip(shipImage, SHIP_WIDTH, SHIP_HEIGHT);
// set it position       
ship.setRefPixelPosition(height/2, width/2);
this.append(ship);

Nhận biết trạng thái bàn phím dùng phương thức getKeyStates()

int keyState = gameCanvas.getKeyStates();

// move right
if ((keyState & GameCanvas.RIGHT_PRESSED)!= 0) {
    ship.moveRight(width);
}  

// move left
if ((keyState & GameCanvas.LEFT_PRESSED)!= 0) {
    ship.moveLeft();
} 

// move up
if ((keyState & GameCanvas.UP_PRESSED) != 0) {
    ship.moveUp();
}  

// move down
if ((keyState & GameCanvas.DOWN_PRESSED) != 0) {
    ship.moveDown(height);
}

Trong GameManager, chúng ta cũng kiểm tra sự va chạm giữa tàu, đạn, và kẻ thù được tạo ngẫu nhiên (hành tinh, ngôi sao), nếu va chạm xuất hiện, thay đổi trạng thái cho phù hợp

private Obstacle checkCollisionWithAsteroids(Sprite t) {
    for (int i =0; i < MAX_OBS;i++) {
        if (obs[i]!=null)
            if (obs[i].collidesWith(t, true)) {
                return obs[i];
            }        
    }        
    return null;    
}

Khi game over, hiện thị điểm cao và dừng GameCanvas bởi phương thức stop

protected void endGame(Graphics g) {
    GameOver=true;
    gameCanvas.stop();
    Font f = g.getFont();
    int h = f.getHeight()+40;
    int w = f.stringWidth("High score")+40;
    g.setColor(250,250,250);
    g.drawString("Score " + score,(width-w)/2,(height-h)/2,g.TOP | g.LEFT);
}

Với kiến thức trên, chúng tôi tin rằng bạn có thể tạo ra những game đơn giản như: câu cá, ếch bắt vật thể

(Author: hoangtuanbs, Finland)

Advertisements

Author: dinhnn

Senior software developer, a technical leader. You can be reached at via email to dinhnguyenngoc@gmail.com, via my blog at dinhnguyenngoc.wordpress.com, and on Twitter @dinhnguyenngoc.

7 thoughts on “Programming 2D Games in J2ME

  1. Bài viết của bạn rất hay, rất cụ thể. Cảm ơn bạn rất nhiều.

  2. ko down dc source code ban oi.

  3. chào anh, em đang tự học j2me anh có thể úp lại code của bài hướng dẫn này cho em tham khảo được không, link trên cùng em không tải được, cám ơn anh

  4. source code tên gì vậy bạn nhiều thư mục quá ko biết chọn cái nào 😀

  5. bạn ơi up lại source code đi bạn vs cho biết tên file là gì luôn nhé

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s