XtGem Forum catalog
logo

Chatbox|Admin nhận làm wap/web, giá cả thương lượng... Thông tin admin tại mỗi bài viết.
Home · Bang hội ·
* Đăng Nhập hoặc Đăng Kí
để sử dụng hết chức năng của diễn đàn.
Hi, Khách!
HomeBang hội » Thủ thuật » Nokia s40 » Khám phá Game API trong MIDP 2.0
Xuống dưới » Khám phá Game API trong MIDP 2.0
avatar by Pham_loi Pham_loi
Chức vụ:
17:19:03, 26-05-2016

J2ME là một nền tảng phổ biến cho cho việc phát triển game cho các thiết bị không dây. Trong bài học này, tôi sẽ giới thiệu cho bạn gói Game API và giúp bạn phát triển một game đơn giản có sử dụng tất cả các lớp thuộc gói này như là một công cụ học tập. Gói này có tên là javax.microedition.lcdui.game, và nó được xây dựng dựa trên các khái niệm mà bạn đã học.


1.Tổng quan J2ME Game API


Chỉ có 5 class trong gói javax.microedition.lcdui.game: GameCanvas, Layer, Sprite, TiledLayerLayerManager. 5 class này đủ để cung cấp một nền tảng hoàn hảo cho việc phát triển các trò chơi.


Lớp Layer là superclass của lớp SpriteTiledLayer. Lớp Layer trừu tượng hành vi của một thành phần trực quan trong một game. Thành phần này có thể là một sprite – thể hiện một đồ họa độc lập (hay một tập hợp các hoạt hình) có thể di chuyển được quanh màn hình game, hay một tiled layer – thể hiện một đồ họa có thể được dùng để tạo ra nền game rộng chỉ với một ít image. Bạn sử dụng các lớp Layer cho việc định vị và khả năng nhìn thấy các đối tượng. Các subclass ghi đè phương thức paint(Graphics g) có nhiệm vụ render các thành phần lên màn hình.


Lớp LayerManager cung cấp một cơ chế tiện lợi cho quản lý nhiều thành phần trực quan của một game (các sprite và tiled layer) bằng cách render lớp thích hợp theo đúng trình tự thích hợp.


Lớp GameCanvas được tạo ra một cách hữu ích bằng cách mở rộng chức năng lớp Canvas. Lớp GameCanvas cung cấp một bộ đệm off-screen, để cho mọi thao tác render được thực hiện trước khi đẩy chúng lên màn hình thiết bị. Lớp GameCanvas cũng cung cấp. Nó cũng cung cấp một cơ chế dễ dùng để truy vấn các phím key hiện hành đang được nhấn bởi người dùng.


Cách tốt nhất để giới thiệu cho bạn những class này là đưa ra môt ví dụ, từ đó ta sẽ hiểu dần dần được mọi khía cạnh của một game.


2.Hướng dẫn vỡ lòng đầu tiên khi xây dựng Game


Một game hay hoạt hình được xây dựng dựa theo nguyên lý việc thực thi có tính chất lặp lại một đoạn mã. Đoạn mã này theo dõi giá trị các biến thể hiện và cập nhật trạng thái game một cách phù hợp. Dựa trên trạng thái game, đoạn mã sau đó draw/paint/repaint màn hình game với các thành phần tạo nên game. Giá trị các biến thể hiện có thể thay đổi bởi vì do tương tác của người dùng hoặc do hành vi bên trong của game.


Việc thực thi có tính lặp lại được tác động bằng cách đặt đoạn mã lặp trong một vòng lặp vô tận. Trước khi vào vòng lặp, một biến thể hiện có thể được kiểm tra kỹ lưỡng nếu game vẫn đang chạy, còn nếu không, vòng lặp có thể bị đẩy ra. Đoạn mã trong vòng lặp sẽ cho phép thread việc thực thi hiện hành ngủ đi một vài giây để kiểm soát tốc độ lúc cập nhật cho các biến thể hiện đã được thực hiện (thực vậy, game screen nhanh như thế nào tùy thuộc vào cách làm tươi).


Trong đoạn mã của bạn phải tuân theo một số điều kiện:
<?php
// main class

public MainClass {

   private 
GameCanvas canvas = new MyGameCanvas();

   public 
MainClass() {

      
// bắt đầu một thread chạy một cách vô tận

      
canvas.start();

   }

   
// mã của bạn

}

// lớp thực sự làm công việc vẽ - the actual drawing class

public MyGameCanvas extends GameCanvas implements Runnable {

   public 
MyGameCanvas() {

      
// mã khởi tạo - instantiation code

   
}

   public 
void start() {

      
// thực hiện việc khởi tạo - do initialization

      // và sau đó bắt đầu một thread - and then start a thread

      
Thread runner = new Thread(this);

      
runner.start();

   }

   private 
void run() {

      
// or while(keeprunning = true)

      // where keeprunning is an instance variable

      
while(true) {

         
// checks if the game has reached

         // some boundary states or special conditions

         
verifyGameState();

         
// gets input from user and

         // updates instance variables

         // that describe the games elements

         
checkUserInput();

         
// paints elements on screen to reflect

         // the current game state using the current

         // graphics object

         
updateGameScreen(getGraphics());

         
// control the rate at which screen updates are done

         
Thread.sleep(milliseconds);

      }

   }

}
?>

Copy code


Ta sẽ sử dụng cấu trúc này để phát triển game trong các mục sau.


3.Xây dựng một J2ME Game: Bắt đầu với GameCanvas


Một lớp GameCanvas là một subclass chuyên biệt hóa của lớp Canvas mà bạn đã biết trong bài trước. GameCanvas được tối ưu cho làm game bởi vì nó cung cấp một bộ đệm off-screen đặc biệt phục vụ mọi thao tác vẽ. Khi mọi công tác vẽ trên bộ đệm này hoàn thành, bộ đệm được render trên màn hình thiết bị bằng cách gọi phương thức flushGraphics(). Việc làm đệm kép này tận dụng lợi thế của việc đem lại các chuyển tiếp mượt mà của các thành phần chuyển động trên màn hình. Kích thước của bộ đệm bằng với kích thước của màn hình thiết bị, và chỉ có một bộ đệm trên mỗi thể hiện GameCanvas.


Lớp GameCanvas cung cấp một cơ chế lưu trữ trạng thái các phím game, đó là một phương pháp có ích để lấy vế tương tác người dùng trong game. Cơ chế này cung cấp một cách đơn giản để theo dõi số lần người dùng đã nhấn một phím cụ thể nào đó. Việc gọi phương thức getKeyStates() trả về một dãy bit thể hiện tất cả các phím game vật lý, cụ thể là 1 cho các phím đã nhấn và 0 là cho các phím chưa được nhấn từ lần cuối cùng phương thức này được gọi. Chỉ những trạng thái game sau là được nhận diện, và được định nghĩa bởi lớp Canvas: DOWN_PRESSED, UP_PRESSED, RIGHT_PRESSED, LEFT_PRESSED, FIRE_PRESSED, GAME_A_PRESSED, GAME_B_PRESSED, GAME_C_PRESSED, GAME_D_PRESSED.


Hãy cùng tạo một game bằng cách kế thừa lớp GameCanvas. Code 1 cho thấy nỗ lực đầu tiên này, còn Code 2 cho thấy MIDlet sẽ được sử dụng để chạy các ví dụ.
<?php
package com
.j2me.part3;

import javax.microedition.lcdui.Image;

import javax.microedition.lcdui.Graphics;

import javax.microedition.lcdui.game.GameCanvas;

import java.io.IOException;

public class 
MyGameCanva extends GameCanvas implements Runnable {

   public 
MyGameCanvas() {

      
super(true);

   }

   public 
void start() {

      try {

         
// create and load the couple image

         // and then center it on screen when

         // the MIDlet starts

         
coupleImg Image.createImage("/couple.gif");

         
coupleX CENTER_X;

         
coupleY CENTER_Y;

      } catch(
IOException ioex) { System.err.println(ioex); }

         
Thread runner = new Thread(this);

         
runner.start();

   }

   public 
void run() {

      
// the graphics object for this canvas

      
Graphics g getGraphics();

      while(
true) { // vòng lặp vô tận

         // based on the structure

         // first verify game state

         
verifyGameState();

         
// check user's input

         
checkUserInput();

         
// update screen

         
updateGameScreen(getGraphics());

         
// and sleep, this controls

         // how fast refresh is done

         
try {

            
Thread.currentThread().sleep(30);

         } catch(
Exception e) {}

     }

   }

   private 
void verifyGameState() {

      
// không làm gì cả

   
}

   private 
void checkUserInput() {

      
// lấy trạng thái các phím

      
int keyState getKeyStates();

      
// tính toàn vị trí của trục x

      
calculateCoupleX(keyState);

   }

   private 
void updateGameScreen(Graphics g) {

      
// the next two lines clear the background

      
g.setColor(0xffffff);

      
g.fillRect(00getWidth(), getHeight());

      
// draws the couple image according to current

      // desired positions

      
g.drawImage(

         
coupleImgcoupleX,

         
coupleYGraphics.HCENTER Graphics.BOTTOM);

      
// đẩy bộ đệm ra màn hình

      
flushGraphics();

   }

   private 
void calculateCoupleX(int keyState) {

      
// determines which way to move and changes the

      // x coordinate accordingly

      
if((keyState LEFT_PRESSED) != 0) {

         
coupleX -= dx;

      }

      else if((
keyState RIGHT_PRESSED) != 0) {

         
coupleX += dx;

     }

   }

   
// the couple image

   
private Image coupleImg;

   
// the couple image coordinates

   
private int coupleX;

   private 
int coupleY;

   
// the distance to move in the x axis

   
private int dx 1;

   
// the center of the screen

   
public final int CENTER_X getWidth()/2;

   public final 
int CENTER_Y getHeight()/2;

}
?>

Copy code

Code 1 – MyGameCanvas: Xây dựng một game canvas

Code 2 cho thấy MIDlet sẽ sử dụng game canvas này:
<?php
package com
.j2me.part3;

import javax.microedition.midlet.MIDlet;

import javax.microedition.lcdui.Display;

public class 
GameMIDlet extends MIDlet {

   
MyGameCanvas gCanvas;

   public 
GameMIDlet() {

      
gCanvas = new MyGameCanvas();

   }

   public 
void startApp() {

      
Display display Display.getDisplay(this);

      
gCanvas.start();

      
display.setCurrent(gCanvas);

   }

   public 
void pauseApp() {

   }

   public 
void destroyApp(boolean unconditional) {

   }

}
?>

Copy code


Code 2 – Lớp MIDlet thử nghiệm


Sử dụng cả hai lớp này, tạo ra một dự án với Toolkit của bạn và sau đó build và chạy ứng dụng. Bạn sẽ cần file ảnh này: [imghttps://huetoday.files.wordpress.com/2010/05/j2me-s1-b3-couple.gif?w=630[/img, tên là couple.gif, trong thư mục res của MIDlet, hoặc bạn có thể sử dụng một ảnh có kích thước tương tự. Hình 1 cho thấy kết xuất mong muốn.


[imghttps://huetoday.files.wordpress.com/2010/05/j2me-s1-b3-h1.gif?w=630[/img


Hình 1 – Sử dụng GameCanvas


Hình ảnh ở giữa màn hình có thể được di chuyển qua trái hay phải bằng các phím game trái phải tương ứng. Trong đoạn mã Code 1, điều này có thể thực hiện được bằng cách truy vấn trạng thái game trong phương thức checkUserInput)[ với trạng thái game này. Như bạn có thể thấy, theo phép so sánh bit OR trạng thái với các hằng được cung cấp trong lớp GameCanvas, bạn có thể dễ dàng quyết định người dùng đã nhấn phím nào và hành động tương ứng. Vị trí tọa độ x của ảnh được di chuyển qua trái hay phải vị trí hiện hành bằng cách cộng thêm hay trừ đi delta x dx[ từ vị trí hiện hành.


Ảnh được render lên màn hình trong phương thức [bupdateGameScreen)[ và sau đó đẩy bộ đệm này lên màn hình thiết bị.


Vòng lặp vô hạn trong phương thức [brun()
theo cấu trúc game tôi đã mô tả ở trên. Vòng lặp này ngủ 30ms trước khi vào vòng lặp khác nhằm xác định user input và làm tươi bộ đệm. Bạn có thể thử nghiệm giá trị này làm chậm hay tăng tốc tỉ lệ làm tươi.


Cuối cùng, chú ý rằng phương thức dựng MyGameCanvas gọi bộ dựng của lớp con GameCanvas với một giá trị tham số là true. Điều này cho biết rằng cơ chế sự kiện phím thông thường, được kế thừa từ lớp Canvas, sẽ bị chặn, vì mã này không yêu cầu các thông báo. Trạng thái được xử lý một cách thỏa đáng bởi thông tin trạng thái phím, được lấy về từ phương thức getKeyStates(). Bằng cách khử cơ chế thông báo cho “phím đã ấn”, “phím đã nhả ra”, và “phím lặp lại”, hiệu suất game được cải thiện.


4.Định rõ các đặc điểm của game


Những gì bạn làm trong một game là phải di chuyển nhân vật chính cứ qua trái hay phải thì chả có gì là thú vị cả. Hãy thay đổi một vài thứ trong thân phát triển game trong Code 1 để cải thiện game tốt hơn một chút. Để bắt đầu, hãy chỉ rõ một giới hạn trong game của bạn. Đó là yếu tố cần thiết để thực hiện, bởi vì nó làm cho game của bạn có kích thước phù hợp qua các thiết bị khác nhau. Để làm điều này, bắt đầu bằng việc định nghĩa một số hằng như trong đoạn mã sau:
<?php
// the game boundary

public static final int GAME_WIDTH 160;

public static final 
int GAME_HEIGHT 160;

// the shifted x,y origin of the game

public final int GAME_ORIGIN_X = (getWidth() - GAME_WIDTH)/2;

public final 
int GAME_ORIGIN_Y = (getHeight() - GAME_HEIGHT)/2;

// the height of sections below and above the couple

public final int SECTION_HEIGHT 64;

// the base on which the couple will move

public final int BASE GAME_ORIGIN_Y GAME_HEIGHT SECTION_HEIGHT;

// the max height the couples can jump

public final int MAX_HEIGHT 32;
?>

Copy code



(Chú ý rằng tôi đã giới thiệu một đặc điểm game cho biết hai nhân vật người có thể nhảy trên màn hình, với hỗ trợ của hằng MAX_HEIGHT). Trên màn hình, những hằng này giúp định nghĩa các giới hạn của game và các thành phần duy nhất của nó (hai nhân vật người), như minh họa trong hình sau:


[imghttps://huetoday.files.wordpress.com/2010/05/j2me-s1-b3-h2.gif?w=630[/img


Hình 2 – Định nghĩa các ranh giới bằng cách sử dụng các hằng game


Dĩ nhiên, bây giờ bạn cần thay đổi phần còn lại của code để sử dụng được những hằng này. Thêm một phương thức mới vào Code 1 tên là buildGameScreen(Graphics g), như sau:
<?php
private void buildGameScreen(Graphics g) {

   
// set the drawing color to black

   
g.setColor(0x000000);

   
// draw the surrounding rectangle

   
g.drawRect(GAME_ORIGIN_XGAME_ORIGIN_YGAME_WIDTHGAME_HEIGHT);

   
// draw the base line

   
g.drawLine(GAME_ORIGIN_XBASEGAME_ORIGIN_X GAME_WIDTHBASE);

  
// draw the maximum line to where the couple can jump to

   
g.drawLine(GAME_ORIGIN_XBASE MAX_HEIGHT,

      
GAME_ORIGIN_X GAME_WIDTHBASE MAX_HEIGHT);

}
?>

Copy code



Ta cũng thêm một lời gọi đến phương thức này trong phương thức updateGameScreen)[ bằng cách thiết lập coupleY=BASE;.


Ảnh hai nhân vật người có thể di chuyển qua trái hay phải với các phím game trái hay phải, nhưng bây giờ chúng ta phải đảm bảo rằng không di chuyển qua ranh giới game. Đây là một vấn đề tồn tại trong Code 1, nhưng trong trường hợp đó, đơn giản là hình ảnh biến mất khỏi màn hình, và ranh giới là cạnh của màn hình. Có vẻ rất là kỳ cục nếu ảnh đi qua các ranh giới. Do đó, việc thay đổi các hành động nhấn phím trái và phải trong phương thức calculateCoupleX() giới hạn việc di chuyển quá các ranh giới. Phương thức được thay đổi như sau:
<?php
private void calculateCoupleX(int keyState) {

   
// determines which way to move and changes the

   // x coordinate accordingly

   
if((keyState LEFT_PRESSED) != 0) {

      
coupleX =

         
Math.max(

         
GAME_ORIGIN_X coupleImg.getWidth()/2,

         
coupleX dx);

   } else if((
keyState RIGHT_PRESSED) != 0) {

      
coupleX =

         
Math.min(

         
GAME_ORIGIN_X GAME_WIDTH coupleImg.getWidth()/2,

          
coupleX dx);;

   }

}
?>

Copy code


Bây giờ phương thức này sử dụng các phương thức Math.max)[ để giới hạn ảnh hai nhân vật người trong phạm vi các ranh giới game. Chú ý rằng việc đó cũng kết hợp chặt chẽ với độ rộng của ảnh trong các tính toán này.


Như đã nói trước đây về việc làm cho ảnh hai nhân vật người nhảy quanh màn hình. Hãy xem cách thực hiện việc này bằng cách thêm một phương thức để di chuyển ảnh theo trục Y, một cách độc lập với người chơi game.


Thêm 3 biến thể hiện mới vào Code 1, gọi là up, jumpHeight, và random, như sau:
<?php
// a flag to indicate which direction the couple are moving

private Boolean up true;

// indicates the random jump height, calculated for every jump

private int jumpHeight MAX_HEIGHT;

// random number generator

public Random random = new Random();
?>

Copy code



Như bạn có thể thấy, jumpHeight được khởi tạo là MAX_HEIGHT. Biến jumpHeight sẽ được tính toán cho mỗi lần nhảy của hai nhân vật người đó và nó sẽ được thiết lập là một giát trị ngẫu nhiên mỗi lúc. Điều này được thể hiện trong phương thức calculateCoupleY() sau đây:
<?php
private void calculateCoupleY(int keyState) {

   
// check if the couple were on the way up

   
if(up) {

      
// if yes, see if they have reached the current jump height

      
if((coupleY > (BASE jumpHeight coupleImg.getHeight()))) {

         
// if not, continue moving them up

         
coupleY -= dy;

      } else if(
coupleY == (BASE jumpHeight coupleImg.getHeight())) {

         
// if yes, start moving them down

         
coupleY += dy;

         
// and change the flag

         
up false;

      }

   } else {

       
// the couple are on their way down, have they reached base?

      
if(coupleY BASE) {

          
// no, so keep moving them down

          
coupleY += dy;

      } else if(
coupleY == BASE) {

          
// have reached base, so calculate a new

          // jump height which is not more than MAX_HEIGHT

          
int hyper random.nextInt(MAX_HEIGHT 1);

          
// but make sure that this it is atleast greater than the image height

          
if(hyper coupleImg.getHeight()) jumpHeight hyper;

          
// move the image up

          
coupleY -= dy;

          
// and reset the flag

          
up true;

      }

   }

}
?>

Copy code



Chú ý rằng vì phương thức này không phụ thuộc vào việc người dùng nhấn các phím lên hay xuống, nó không sử dụng thông tin keyState. Nhưng dù sao giá trị này đã được chuyển cho nó, để duy trì sự phù hợp với phương thức calculateCoupleX)[. Một khi phương thức chạm tới độ cao bước nhảy này, nó bắt đầu di chuyển hướng ngược lại cho đến khi nó chạm tới [bBASE. Tại vị trí này, một giá trị độ cao bước nhảy mới, giữa MAX_HEIGHT và độ cao ảnh hai nhân vật người, được tính toán một cách ngẫu nhiên và hai nhân vật người lại tiếp tục nhảy.


Toàn bộ tác động có thể được thực hiện bằng cách ấn phím trái hay phải từ phía người dùng. Một snapshot minh họa:


[imghttps://huetoday.files.wordpress.com/2010/05/j2me-s1-b3-h3.gif?w=630[/img


Hình 3 – Một game snapshot


5.Xây dựng một J2ME Game: Tạo các nền sau sử dụng lớp TiledLayer


Trong mục này, bạn sẽ thêm một số màu sắc vào game bằng cách tạo ra một nền sau (background) sử dụng lớp TiledLayer. Game được chia ra thành 3 phần: phần đầu có thể tưởng tượng là bầu trời, phần giữa là hình hai nhân vật người nhảy trên mặt đất, và phần dưới là biển. Ba phần này có thể được thiết kế dễ dàng bằng việc sử dụng 3 ảnh kích thước 32×32 pixel, mỗi ảnh cho mỗi phần. Tuy nhiên, mỗi vùng thường lớn hơn 32×32 pixel, và lớp TiledLayer được dùng để định nghĩa các vùng rộng như thế với các bức ảnh nhỏ.


Để bắt đầu, chia màn hình game thành các ô vuông 32×32 px và đánh số các hàng và cột, bắt đầu từ chỉ số index là 0. Hình sau cho thấy một nền sau 5×5 ô:


[imghttps://huetoday.files.wordpress.com/2010/05/j2me-s1-b3-h4.gif?w=630[/img


Hình 4 – Chia màn hình game thành các ô riêng biệt


Vì vậy, các ô (0, 0) đến (1, 4) được vẽ bằng ảnh bầu trời; các ô (2, 0) đến (2, 4) vẽ mặt đất, và các ô (3, 0) đến (4, 4) vẽ biển. Bạn sẽ làm việc này với các ảnh sau:


[imghttps://huetoday.files.wordpress.com/2010/05/j2me-s1-b3-h5.gif?w=630[/img


Hình 5 – Các ảnh nền sau


Ô 32×32 px đầu tiên thể hiện ảnh mặt đất, ô thứ hai thể hiện biển, và ô thứ 3 thể hiện bầu trời. Khi bạn sử dụng lớp TiledLayer, ba ảnh được đánh số từ index 1 (không phải 0; vì thế, mặt đất là 1, biển là 2, và bầu trời là 3). Lớp TiledLayer sẽ sử dụng một bức ảnh này và chia nó ra thành 3 ảnh riêng biệt dùng cho việc render nền sau của game. Trong trường hợp của chúng ta, ta muốn TiledLayer render một ô nền sau 5×5 bằng việc sử dụng các ô 32×32 px. Ta làm điều đó như sau:
<?php
// load the image

backgroundImg Image.createImage("/tiledLayer1.gif");

// create the tiledlayer background

background = new TiledLayer(55backgroundImg3232);
?>

Copy code



Như bạn có thể thấy, hai tham số đầu tiên cho phương thức dựng TiledLayer thể hiện kích thước tổng của nền sau, tham số tiếp theo là ảnh bạn dùng để cắt, và hai tham số cuối cùng thể hiện kích thước cho mỗi ô. Kích thước này sẽ được sử dụng bởi lớp TiledLayer để cắt ảnh thành các ô nền sau độc lập.


Việc còn lại bây giờ là đặt vào mỗi ô với ảnh tương ứng của nó. Mã thực hiện tạo nền sau được cho bên dưới trong một phương thức tên là createBackground)[ thuộc lớp MyGameCanvas. Một khi thực hiện xong, thêm một lời gọi để vẽ background này sử dụng background.paint(g) ở cuối phương thức buildGameScreen().
<?php
// creates the background using TiledLayer

private void createBackground() throws IOException {

   
// load the image

   
backgroundImg Image.createImage("/tiledlayer1.gif");

   
// create the tiledlayer background

   
background = new TiledLayer(55backgroundImg3232);

   
// array that specifies what image goes where

   
intcells = {

      
33333// sky

      
33333// sky

      
11111// earth

      
22222// sea

      
22222  // sea

   
};

   
// set the background with the images

   
for (int i 0cells.lengthi++) {

      
int column 5;

      
int row = (column)/5;

      
background.setCell(columnrowcells[i);

   }

   
// set the location of the background

   
background.setPosition(GAME_ORIGIN_XGAME_ORIGIN_Y);

}
?>

Copy code



Kết quả cuối cùng như sau:


[imghttps://huetoday.files.wordpress.com/2010/05/j2me-s1-b3-h6.gif?w=630[/img


Hình 6 – Game với background đã được thêm vào


6.Xây dựng một J2ME Game: Sprite và lớp LayerManager


Cho đến lúc này tôi đã sử dụng một ảnh hình hai nhân vật người để cho bạn thấy một thành phần điển hình của game. Thành phần này được render hiện hành như là một ảnh; nhưng ta có cảm giác như nó được render ra một hình sprite. Một sprite là một thuật ngữ theo cách nói của game nói đến bất kỳ thành phần trực quan nào, thường là một ảnh hoạt hình, có thể được di chuyển quanh màn hình một cách độc lập với các thành phần khác. Lớp Sprite được dùng để thể hiện các sprite trên các game MIDP 2.0 API. Lớp này cung cấp các phương thức để làm hoạt hình sprite dựa trên một số ảnh, tương tự với cách các background được tạo ra khi sử dụng lớp TiledLayer. Quan trọng hơn, nó cung cấp các phương thức kiểm tra các va chạm với các thành phần game khác, bao gồm các ảnh, các sprite hay các lớp tiledlayer.


Hãy cùng bắt đầu bằng cách chuyển đổi đoạn mã có ảnh hình hai nhân vật người có sẵn thành một sprite. Để thấy được hoạt hình, tôi sẽ sử dụng ảnh trong Hình 7, là ảnh hai nhân vật người được sao chép với một màu khác thành 2 frame khác nhau, mỗi frame 10×10 px.


[imghttps://huetoday.files.wordpress.com/2010/05/j2me-s1-b3-h7.gif?w=630[/img


Hình 7 – Các frame cho hoạt hình Sprite


Tương tự với lớp TiledLayer, lớp Sprite yêu cầu kích thước cho mỗi frame, được chuyển vào phương thức khởi tạo của nó:
<?php
coupleSprite 
= new Sprite(coupleImg1010);
?>

Copy code



Đoạn mã này được thêm vào sau đoạn mã tạo ra ảnh hai nhân vật người, tạo ra một sprite hai nhân vật người với 2 frame 10x10px, được đánh số từ 0. Vì vậy, để luân phiên giữa các ảnh sprite, bạn có thể gọi phương thức nextFrame)[. Trong trường hợp này, thêm coupleSprite.nextFrame)[.


Bây giờ bạn không muốn ảnh hai nhân vật người được vẽ ra trên màn hình. Trước khi sprite hai nhân vật người có thể được vẽ ra trên màn hình, bạn cần định nghĩa một điểm pixel tham chiếu đến nó. Hãy nghĩ rằng đó là một điểm bắt đầu – điểm gốc, mà mọi thao tác vẽ được thực hiện. Theo mặc định, sprite được vẽ ra có góc trên bên trái đóng vai trò là điểm bắt đầu của nó. Tương tự với cách bạn thiết lập tham chiếu đến ảnh hai nhân vật người khi sử dụng mã Graphics.HCENTER | Graphics.BOTTOM, bạn cần định nghĩa một điểm pixel tham chiếu cho sprite, như sau:
<?php
coupleSprite
.defineReferencePixel(coupleSprite.getWidth()/2coupleSprite.getHeight());
?>

Copy code



Thêm dòng mã này vào sau mã tạo ra sprite như đã nói ở trước. Bây giờ, thay vì định vị sprite dựa trên điểm gốc, bạn sẽ định vị nó dựa trên pixel tham chiếu này, như sau:
<?php
coupleSprite
.setRefPixelPosition(coupleXcoupleY);

coupleSprite.paint(g);
?>

Copy code



Dòng cuối cùng của đoạn mã trên vẽ ra sprite trên đối tượng đồ họa được chuyển cho nó g[. Như mong đợi, bạn sẽ cần chèn hai dòng mã này vào phương thức [bupdateGameScreen() thay thế vào dòng mã vẽ hình hai nhân vật người. Kết quả cuối cùng sẽ gần giống với kết quả trước, ngoại trừ hai nhân vật người nhảy trên màn hình sẽ được thay thế với hai nhân vật người nhấp nháy lung linh!


Trước khi tiếp tục, hãy đảm bảo rằng tất cả những thay đổi của bạn cho biến coupleImg vào coupleSprite trong các phương thức calculateX)[.


Quản lý các Layer sử dụng lớp LayerManager


Nhớ lại cả hai lớp SpriteTiledLayer kế thừa từ lớp Layer. Một lớp chỉ có thể chứa ít nhất một lớp TiledLayer và vài lớp Sprite. Khi có quá nhiều layer để kiểm soát, lớp LayerManager xuất hiện lúc cần thiết. Lớp này cung cấp các phương thức thêm, xóa, hay chèn các layer trong một game, và cũng cung cấp một phương thức đơn để vẽ tất cả những layer này vào đối tượng Graphics bên dưới. Điều này có nghĩa là bạn không cần gọi phương thức paint() một cách độc lập cho mỗi layer của một game.


Một thể hiện của lớp LayerManager được tạo ra sử dụng bộ khởi tạo không đối số của nó. Các layer sau đó được thêm vào, loại bỏ đi, hoặc chèn vào bằng các sử dụng phương thức append(Layer layer), remove(Layer layer), và insert(Layer layer, int index). Thứ tự mà các layer được thêm vào rất quan trọng, bởi vì thứ tự này quyết định layer nào được vẽ ra đầu tiên, và thứ tự này theo thứ tự trục z-order. Layer ở index 0 được vẽ ra trên hết các layer khác, và từ đó, layer này là nằm gần người dùng nhất, và cứ tiếp tục như thế.


Trong game của chúng ta, phương thức start() bây giờ cần được thay đổi, như sau:
<?php
// creates the layermanager

manager = new LayerManager();

// and adds layers to it

manager.append(coupleSprite);

// creates the game background

createBackground();

manager.append(background);
?>

Copy code


Như bạn có thể thấy, coupleSprite sẽ là layer gần người dùng nhất và layer background sẽ là layer xa người dùng nhất, dựa theo chỉ số của chúng. Phương thức buildGameScreen)[, và vì thế dòng lệnh [bbackground.paint(g) cần được loại bỏ từ phương thức này. Cuối cùng, như trong mục trước, bạn đã dùng coupleSprite để vẽ ra màn hình thay vì coupleImage. Bây giờ dù điều đó không được yêu cầu, vì LayerManager sẽ làm điều đó cho bạn. Loại bỏ coupleSprite.paint(g) ra khỏi phương thức updateGameScreen)[. Như bạn có thể thấy, tất cả các lời gọi đến các phương thức paint)[ của LayerManager. Hai tham số cuối cùng thể hiện vị trí mà manage sẽ vẽ. Khi đó backgroundcarSprite chịu trách nhiệm cho vị trí của riêng chúng, bạn có thể bỏ qua những tham số này nếu có (tức là, vẽ từ vị trí gốc thiết bị).


Code 3 cho thấy phương thức updateGameScreen() đã được sửa lại. Các dòng bị loại bỏ vẫn được giữ lại trong các comment để dễ dàng nhận thấy các thay đổi:
<?php
private void updateGameScreen(Graphics g) {

   
// the next two lines clear the background

   
g.setColor(0xffffff);

   
g.fillRect(00getWidth(), getHeight());

   
// creates the game borders

   
buildGameScreen(g);

   
// draws the couple image according to current

   // desired positions

   /*g.drawImage(

      coupleImg, coupleX,

      coupleY, Graphics.HCENTER | Graphics.BOTTOM);*/

   // animates the sprite

   
coupleSprite.nextFrame();

   
// moves the sprite based on its reference pixel

   
coupleSprite.setRefPixelPosition(coupleXcoupleY);

   
// paints it on the buffer

   // coupleSprite.paint(g);

   // the manager paints all the layers

   
manager.paint(g00);

   
// this call paints off screen buffer to screen

   
flushGraphics();

}
?>

Copy code



Code 3 – Cập nhật lại phương thức updateGameScreen()


7.Thêm nhiều Sprite hơn nữa và nhận diện va chạm


Một sprite mà chỉ có nhảy qua nhảy lại thì đâu có vui phải không các bạn? Đã đến lúc thêm một sprite khác, ví dụ như là một chiếc xe xuất hiện vài nơi trên màn hình. Hai nhân vật người cần va chạm vào chiếc xe này! Càng nhiều xe bị hai người này chạm vào từng lượt, điểm số càng cao hơn.


Với các mục tiêu game đã rõ ràng, trước tiên hãy tạo một class sẽ theo dõi thời gian để game có thể dừng lại một khi hết thời gian. Code 4 cho thấy mã cho lớp Clock:
<?php
package com
.j2me.part3;

import java.util.TimerTask;

public class 
Clock extends TimerTask {

   
int timeLeft;

   public 
Clock(int maxTime) {

      
timeLeft maxTime;

   }

   public 
void run() {

      
timeLeft--;

   }

   public 
int getTimeLeft() { return this.timeLeft; }

}
?>

Copy code

Code 4 – Lớp Clock sẽ theo dõi thời gian game

Lớp Clock kế thừa lớp TimerTask, có phương thức run() được thực hiện sau mỗi khoảng thời gian định trước. Ở đây, phương thức này giảm bớt biến maxTime qua mỗi giây đồng hồ, nhằm giúp chúng ta theo dõi thời gian. Để sử dụng lớp Clock, tạo ra và khởi động nó trước vòng lặp vô tận bên trong phương thức run() của lớp MyGameCanvas khi được thực thi, như sau:
<?php
// before going in the loop, start the timer clock with a

// 30 seconds countdown

clock = new Clock(30);

new 
Timer().schedule(clock01000);
?>

Copy code


Dĩ nhiên, bây giờ vòng lặp vô tận phải được kiểm soát với một flag nhằm dừng vòng lặp khi đang chạy khi hết thời gian. Để làm điều này, định nghĩa một flag có tên là stop, như dưới đây:
<?php
// the flag that tells the game to stop

private Boolean stop false;
?>

Copy code



Đưa nó vào vòng lặp while với while(!stop) và nhập các dòng mã đầu tiên trong phương thức verifyGameState():
<?php
private void verifyGameState() {

   if(
clock.getTimeLeft() == 0) {

      
stop true;

      return;

   }

}
?>

Copy code



Cuối cùng, người dùng cần được biết thời gian còn lại trong game. Để làm điều này, thêm một phương thức tên là showTimeLeft(Graphics g) như dưới đây:
<?php
private void showTimeLeft(Graphics g) {

   
// what does the clock say

   
int timeLeft clock.getTimeLeft();

   
// if less than 6 seconds left

   // flicker time with red and black

   
if(timeLeft 6) {

      if((
timeLeft 2) == 0)

         
g.setColor(0xff0000);

      else

         
g.setColor(0x000000);

   }

   
// draw the time left string

   
g.drawString("Time Left: " timeLeft " seconds"000);

   
// reset the color

   
g.setColor(0x000000);

}
?>

Copy code



Phương thức này được gọi ở cuối phương thức buildGameSreen(). Hình 8 cho thấy một snapshot của game:


[imghttps://huetoday.files.wordpress.com/2010/05/j2me-s1-b3-h8.gif?w=630[/img


Hình 8 – Game có thông báo thời gian còn lại


Giờ là lúc thêm một vài sprite mới vào trong game. Code 5 là đoạn mã cho chiếc xe trong một class tên là CarSprite. Đoạn mã này sử dụng bức ảnh một chiếc xe trong hình 9.


[imghttps://huetoday.files.wordpress.com/2010/05/j2me-s1-b3-h9.gif?w=630[/img


Hình 9 – Hình ảnh một car sprite
<?php
package com
.j2me.part3;

import java.util.Random;

import javax.microedition.lcdui.Image;

import javax.microedition.lcdui.game.Sprite;

import javax.microedition.lcdui.game.LayerManager;

public class 
CarSprite implements Runnable {

   public 
CarSprite(MyGameCanvas parent) {

      
this.parent parent;

      
this.manager parent.getManager()
   }

   public 
void start() {

      
// first load the car image

      
try {

         
carImage Image.createImage("/car.gif");

      } catch(
Exception e) { System.err.println(e); return; }

      
// next start the thread that will display cars

      // are random locations

      
runner = new Thread(this);

      
runner.start()
   }

   public 
void run() {

      try {

         while(
true) {

            
// create a random car

            
randomCar();

            
// wait before creating another one

            
Thread.currentThread()Sleep(500);

         }

      } catch(
Exception e) { System.err.println(e); }

   }

   
// creates and displays a car at a random location

   
private void randomCar() {

      
// if maximum cars are being shown return

      
if(currentCars == MAX_CARS) return;

      
// create a new car sprite

      
carSprite = new Sprite(carImage1010);

      
// generate the random places where cars may appear

      
int randomCarX parent.getRandom().nextInt(parent.GAME_WIDTH);

      
int randomCarY =

         (
parent.BASE -

         
parent.getRandom().nextInt(parent.MAX_HEIGHT 1) -

         
carSprite.getHeight());

      
// make sure that these places are within bounds

      
if(randomCarX parent.GAME_ORIGIN_X
         
randomCarX parent.CENTER_X;

      if(
randomCarY < (parent.BASE parent.MAX_HEIGHT)
         
randomCarY parent.CENTER_Y;

      
// set this newly created car sprite in its random position

      
carSprite.setPosition(randomCarXrandomCarY);

      
// add it to the manager at index 0

      
manager.insert(carSprite0);

      
// increase the no of cars created

      
currentCars++;

   }

   public 
void checkForCollision() {

      
// if there are no cars being shown (only background and couple)

      
if(manager.getSize() == 2) return;

      
// iterate through the layers, remember don't worry about

      // the last two because they are the couple and background

      
for(int i 0< (manager.getSize() - 2); i++) {

         
// if collision occurs

         
if(parent.getCoupleSprite().collidesWith(

            (
Sprite)manager.getLayerAt(i), true)) {

            
// remove the offending car

            
manager.remove(manager.getLayerAt(i));

            
// reduce the no of cars on display

            
currentCars--;

            
// and increase the no of cars hit

            
carsHit++;

         }

      }

   }

   
// the no of cars hit

   
public int getCarsHit() {

      return 
carsHit;

   }

   
// the car sprite

   
private Sprite carSprite;

   
// the car image

   
private Image carImage;

   
// the no of current cars in display

   
private int currentCars;

   
// the parent canvas

   
private MyGameCanvas parent;

   
// the parent canvas's layer manager

   
private LayerManager manager;

   
// runner

   
private Thread runner;

   
// tracks the no of cars hit

   
private int carsHit;

   
// the maximum no of cars to create

   
private static final int MAX_CARS 20;

}
?>

Copy code



Code 5 – Đoạn mã tạo thêm vài car sprite


Lớp CarSprite cài đặt giao diện Runnable, để sinh ra vài car sprite mới cứ mỗi nửa giây. Phương thức run)[ sau khi chờ 500ms. Phương thức randomCar() kiểm tra nếu số car sprite đang có không được vượt quá giới hạn, sau đó tạo ra một sprite mới sử dụng hình ảnh được nạp lên trước đó. Sau đó nó tính toán một vị trí ngẫu nhiên cho sprite này xuất hiện, và đảm bảo rằng vị trí ngẫu nhiên này ở trong phạm vi của game. Nó còn thiết lập sprite vào LayerManager ở mức index là 0, vì thế làm cho sprite đó gần với người dùng nhất.


Lớp này cũng cung cấp một phương thức để kiểm tra va chạm của hai nhân vật người với các chiếc xe ngẫu nhiên. Phương thức checkForCollision)[ trong lớp Sprite để kiểm tra va chạm. Phương thức này trả về true khi va chạm xảy ra, và chấp nhận một layer, một hình ảnh, hoặc một thứ khác khi có va chạm. Phương thức này cũng chấp nhận một flag cho biết nếu có nhận ra va chạm thì sẽ chú ý tới các pixel trong suốt quanh một hình ảnh, hay chỉ cho các pixel mờ. Khi một va chạm được nhận diện, số car bị chạm vào được tăng lên và số car thấy bị giảm đi.


Để sử dụng lớp CarSprite, chèn các dòng mã sau vào cuối phương thức start() trong lớp MyGameCanvas.
<?php
// create the car sprite thread and start it

carSprite = new CarSprite(this);

carSprite.start();
?>

Copy code


Và cũng thêm dòng mã sau vào cuối phương thức verifyGameState():


<?php
carSprite
.checkForCollision();
?>

Copy code



Như thế, thread CarSprite bắt đầu đẻ ra nhiều car mới, với tối đa là MAX_CARS chiếc xe. Một khi người dùng chạm vào một chiếc xe bằng cách di chuyển hai nhân vật người đến chiếc xe đó, thì chiếc xe biến mất. Điều này được kiểm tra trong phương thức verifyGameState)[ trên thread CarSprite. Nhiều chiếc xe hơn vẫn cứ xuất hiện trong khi thời gian hết dần. Hình 10 cho thấy một game đang trong quá trình như vậy.


[imghttps://huetoday.files.wordpress.com/2010/05/j2me-s1-b3-h10.gif?w=630[/img


Hình 10 – Một game điển hình đang chạy sau khi thêm một số sprite


Những gì còn lại bây giờ là thông báo cho người dùng số chiếc xe mà họ đã phá được. Sau khi vòng lặp while() thoát ra, thêm một lời gọi vào một phương thức mới tên là showGameScore(getGraphics()) như dưới đây:
<?php
// at the end of the game show the score

private void showGameScore(Graphics g) {

   
// create a base rectangle

   
g.setColor(0xffffff);

   
g.fillRect(0CENTER_Y 20getWidth(), 40);

   
g.setColor(0x000000);

   
// and show the score

   
g.drawString("You hit " +

      
carSprite.getCarsHit() + " cars.",

      
CENTER_XCENTER_Y,

      
Graphics.HCENTER Graphics.BASELINE);

   
flushGraphics();

}
?>

Copy code



Phương thức này vẽ một hình chữ nhật nhỏ ở giữa màn hình lúc kết thúc game, hiện ra số xe đã bị phá bởi người chơi. Một game điển hình kết thúc như sau:


[imghttps://huetoday.files.wordpress.com/2010/05/j2me-s1-b3-h11.gif?w=630[/img


Hình 11 – Một kết thúc điển hình và thông điệp hiển thị cuối cùng


Dĩ nhiên bạn có thể hiển thị thông tin này theo định dạng và vị trí bất kỳ mà bạn muốn.


8.Kết luận


Tuy bài 3 này hơi dài, nhưng bạn đã học được cách sử dụng các lớp MIDP 2.0 API với một ví dụ tương đối tổng quan nhất và game đã chạy thành công. Bạn cũng học được những cơ bản về quá trình xây dựng game qua ví dụ đó.


Trong phần tiếp theo của series này, bạn sẽ học cách thêm multimedia vào MIDlet của bạn,thậm chí một số thứ có thể hữu ích trong J2ME game. Bạn cũng sẽ được học cách sử dụng record-store management API để lưu trữ thông tin vào thiết bị.


Dịch từ today.java.net

Like: 0
Lên trên  Tổng số: 1







Trực Tuyến: Khách: 1
Diễn đàn teen Việt Nam
CopyRight 2014