Phát triển game đơn giãn trên Mobile

Cuối cùng, trong phương thức run(), bạn gọi phương thức sendSms() để gửi message: public void run() { [.] case (ACTION_SEND_INVITE): // invite another player to play String inviteMessage = ” invites you to play Transnoid!”; network.sendSms(inviteNumber.getString(), inviteName.getString() + inviteMessage); break; } [.] }

docx40 trang | Chia sẻ: truongthinh92 | Lượt xem: 1821 | Lượt tải: 0download
Bạn đang xem trước 20 trang tài liệu Phát triển game đơn giãn trên Mobile, để xem tài liệu hoàn chỉnh bạn click vào nút DOWNLOAD ở trên
SENSITIVE, NON_PREDICTIVE, INITIAL_CAPS_WORD, và INITIAL_CAPS_SENSITIVE. Ví dụ, chỉ cho phép địa chỉ email được phép nhập trong TextBox, bạn thiết lập flag TextField.EMAILADRR sử dụng phương thức setConstrains(), ngoài ra để kết hợp nhiều ràng buộc cùng lúc, bạn sử dụng toán tử OR giữa các flag. Ví dụ: setConstraints(TextField.EMAILADDR | TextField.UNEDITABLE); List: Một List chứa một danh sách các chọn lựa. Khi List được biểu diễn trên màn hình, người dùng có thể tương tác với nó bằng cách chon lựa các phần tử, di chuyển qua lại giữa các phần tử của List. Nó có các kiểu cấu hình sau: Choice.EXCLUSIVE: chỉ có 1 phần tử được chọn lựa. Choice.MULTIPLE: có thể có nhiều phần tử được chọn lựa. Choice.IMPLICIT: phần tử được hightlight được chọn lựa. Form: Nó chứa nhiều item, bất kỳ lớp nào extends từ lớp Item để có thể được chứa trong một Form. Việc thực thi xử lý các layout, traversal, và scrolling. Nội dung của Form có thể được cuộn lại với nhau. Các loại Item có thể được thêm vào trong Form: StringItem: là một label và không cho phép người dùng sửa lên nó. Item này có thể chứa một tiêu đề và một text, và cả hai đều có thể null. DateField: cho phép người dùng nhập ngày date/time một trong 3 dạng sau: DATE, TIME và DATE_TIME. TextField: Tương tự như TextBox. ChoiceGroup: Tương tự như List. Gauge: sử dụng để mô phỏng process bar, tuy nhiên nó có thể sử dụng trong kiểu tương tác bởi người dùng, ví dụ nếu bạn muốn dùng nó để show một Volume Control. ImageItem: Nắm giữ một image. CustomItem: là một lớp ảo abstract cho phép các subclass tạo ra giao diện riêng, tương tác riêng và cơ chế thông báo riêng của nó. Nếu bạn muốn một phần tử UI khác so với các phần tử được cung cấp thì bạn có thể tạo ra subclass. Giao diện người dùng cấp thấp(Low-level User Interface): Các API của low-level user interface( như lớp Canvas) được thiết kế cho các ứng dụng cần sự sắp đặt và điều khiển các phần tử graphics một cách chính xác cũng như truy cập vào các low-level input event. Ví dụ điển hình là game board, một chart object hay một graph. Sử dụng low-level user interface, một ứng dụng có thể: Kiểm soát những gì được vẽ trên màn hình. Điều khiển được các sự kiện primitive như các sự kiện nhấn phím(key press) và nhả phím(key release) Truy cập vào cụ thể từng phím và các thiệt bị đầu vào khác. Ngoài ra, trong MIDP 2.0 còn cung cấp javax.microediton.lcdui.game. Gói này bao gồm 5 class dùng để thiết kế cho các game, đó là : GameCanvas, LayerManger, Layer, Sprite và TiledLayer. Bạn có thể tìm hiểu phần này sau. Ví dụ về giao diện người dùng: Đối với mỗi màn hình game, chúng ta tạo một phương thức init[ScreenName] để khởi tạo màn hình và trả về đối tượng Displayable được tạo ra: Đối với Main Menu, bạn sử dụng component List để biểu diễn main options. Ví dụ: public Displayable initMainForm() {  if (mainForm == null) {     // creates a implicit List where the current element is     // the selected     mainForm = new List("Menu",   List.IMPLICIT);     // append list options     mainForm.append("New Game",   null);     mainForm.append("Options",   null);     mainForm.append("Scores",   null);     mainForm.append("Help", null);     mainForm.append("Exit", null);     // adds a select Command     comSelect = new   Command("Select", Command.ITEM, 1);     mainForm.setSelectCommand(comSelect);     // adds a listener to the form     mainForm.setCommandListener(this);   }     return mainForm;  } Đối với Menu Settings, bạn chọn phần tử Form, và thêm một đối tượng ChoiceGroup để tùy chỉnh âm thanh: public Displayable initSettingsForm() {   // check if already created   if (settingsForm == null) {     settingsForm = new Form("Settings");     settingsForm.addCommand(initBackCommand());     settingsForm.setCommandListener(this);     // creates a choice Group for sound options     soundChoice = new ChoiceGroup("Sound", List.EXCLUSIVE);          soundChoice.append("On", null);     soundChoice.append("Off", null);     // appends the choice to the form     settingsForm.append(soundChoice);        }   return settingsForm; } Đối với Help Screen, bạn chọn phần tử Form với static message: public Displayable initHelpForm() {     if (helpForm == null) {       helpForm = new Form("Help");       helpForm           .append("User cursors to move your pad, don't let "+               "the ball go by you, hit all the bricks!");       helpForm.setCommandListener(this);       helpForm.addCommand(initBackCommand());     }     return helpForm;   } Để giới thiệu High Score, bạn sử dụng một Form với các item của nó là TextField và DateField: public Displayable initNewHighScore(int score, int pos) {     if (newHighScoreForm == null) {       newHighScoreForm = new Form("New High Score");       newHighScoreForm.setCommandListener(this);       // create items       highScoreName = new TextField("Name", "", 20, TextField.ANY);       highScoreValue = new StringItem("Score", Integer.toString(score));       highScorePosition = new StringItem("Position", Integer.toString(pos));       // create save command       highScoreSave = new Command("Save", Command.OK, 1);            // append command and itens to screen       newHighScoreForm.addCommand(highScoreSave);       newHighScoreForm.append(highScoreName);       newHighScoreForm.append(highScoreValue);       newHighScoreForm.append(highScorePosition);     }     // update score     highScoreValue.setText(Integer.toString(score));     // update pos     highScorePosition.setText(Integer.toString(pos)+1);     return newHighScoreForm;   } Màn hình game sẽ được đề cập trong bài tiếp theo. Bây giờ, hãy tạo một phương thức giả lập kết thúc game và sử dụng nó để thay thế: public void endGame(int lifes, int score, int time) {     Displayable nextScreen = initMainForm();     String message;     if (lifes == 0) {       message = "Game Over!!";     } else {       message = "You Win!";     }     int pos = isHighScore(score);     if (pos != -1) {       nextScreen = initNewHighScore(score, pos);     }      display(new Alert(message, message, null, AlertType.INFO), nextScreen);   } Bây giờ, tất cả các phương thức đã được tạo ra, bạn link chúng đến phương thức commandAction(). Rewrite code: public void commandAction(Command cmd, Displayable display) {   // check what screen is being displayed   if (display == mainForm) {     // check what command was used     if (cmd == comSelect) {       switch (mainForm.getSelectedIndex()) {       case (0):         // At the moment just go directly to the end of the game         endGame(1, 200, 50);         break;       case (1):         display(initSettingsForm());         break;       case (2):         display(initScoreForm());         break;       case (3):         display(initHelpForm());         break;       case (4):         exit();         break;       }     }   } else if (display == highScoreForm) {     if (cmd == comBack) {       display(initMainForm());     }   } else if (display == settingsForm) {     if (cmd == comBack) {       soundOn = soundChoice.getSelectedIndex() == 0;       display(initMainForm());     }   } else if (display == helpForm) {     if (cmd == comBack) {       display(initMainForm());     }   } else if (display == newHighScoreForm) {       if (cmd == highScoreSave) {         int pos = Integer.parseInt(highScorePosition.getText())-1;         // advance all the scores         for ( int i = scores.length-1; i > pos ; i--){           scores[i].name  = scores[i-1].name;           scores[i].value = scores[i-1].value;           scores[i].when  = scores[i-1].when;         }         // insert new score         scores[pos].name = highScoreName.getString();         scores[pos].value = Integer.parseInt(highScoreValue.getText());         scores[pos].when = new Date();                display(initScoreForm());       }     } }Tất cả các logic menu cho các MIDlet được xác định bên trong phương thức commandAction() này. Quyết định làm gì tiếp theo phụ thuộc vào màn hình hiển thị và command được chọn lựa. Từ menu chính, tôi chỉ đơn giãn chuyển hướng người dùng đến mỗi màn hình cụ thể. Các màn hình hiện tại chỉ có một back command, chỉ duy nhất một form NewHighScores có command save dùng để lưu thông tin về điểm số(score). Bạn lưu ý cách thức sử dụng phương thức display(), vì đây là cách đơn giãn để kích hoạt đối tượng Displayable. public void display(Displayable display) {   // shows display  in the screen.   Display.getDisplay(this).setCurrent(display); } Bài tiếp theo sẽ mô tả làm thế nào để cài đặt một Game Screen. Phát triển game đơn giãn trên Mobile(P3) Ở bài trước, chúng ta đã hoàn thành một giao diện menu cho game. Tuy nhiên, màn hình Game Screen vẫn chưa được tạo ra. Mục đích của bài này là sinh ra một Game Screen có giao diện như hình dưới: Các thành phần giao diện cấp cao không thể sử dụng cho game screen bởi vì chúng ta phải điều khiển tất cả các phần tử trong game khi chúng được vẽ và làm thế nào để game tương tác trở lại với keypad. Để làm được điều này, chúng ta phải sử dụng các class giao diện low-level. Các class thuộc nhóm low-level cho phép bạn kiểm soát chi tiết các phần tử và sự kiện trên màn hình game. Sử dụng các class này, bạn có thể xác định rõ vị trí, màu sắc và kích thước. Cũng chính vì vậy, bạn phải thiết kế màn hình game screen cho mỗi loại màn hình ứng với mỗi thiết bị. Diagram sau cho biết các lớp chính trong giao diện low-level : Với entry point là lớp Canvas, nó cho phép bạn truy cập các sự kiện của hệ thống: keyPressed(), keyReleased(), keyRepeated() thông báo tới Canvas khi keypad được sử dụng. pointerPressed(), pointerDragged(), pointerReleased() thông báo tới Canvas khi pointer được sử dụng, đây là các phương thức xử lý trong các màn hình cảm ứng. paint() thông báo đến Canvas khi nó cần vẽ lên màn hình, phương thức này dùng để truy cập vào đối tượng Graphics. getWidth(), getHeight() truy cập vào kích thước hiện tại của màn hình được vẽ. Lớp Graphics cung cấp các phương thức để vẽ trực tiếp lên màn hình: drawLine() vẽ đường thẳng. drawRect(), fillRect() vẽ hay đổ màu một rectangle lên màn hình. drawArc() vẽ một cung(arc) lên màn hình, có thể dùng nó để vẽ đường tròn. drawChar() vẽ một ký tự lên màn hình. drawString() vẽ một chuỗi lên màn hình. drawImage() vẽ một bitmap image lên màn hình setFont() thiết lập Font chữ. setColor() thiết lập màu. Ngoài ra trong MIDP 2.0 còn nhiều phương thức khác, bạn tự tìm hiểu thêm. Để tạo Game Screen, bạn phải tạo một lớp extends từ lớp Canvas và cài đặt phương thức paint(). import javax.microedition.lcdui.Canvas; import javax.microedition.lcdui.Graphics; public class MyCanvas extends Canvas{ int width; int height; public MyCanvas() { } protected void paint(Graphics g) { // stores width and height width = getWidth(); height = getHeight(); // set background color g.setColor(0,0,0); // clear screen g.fillRect(0, 0, width, height); // draw a red circle that represents a ball g.setColor(255,0,0); g.drawArc(100, 100, 5, 5, 0, 360); // draws a blue rectangle for the pad g.setColor(0,0,255); g.fillRect(100, 200, 15, 15); } } Để kích hoạt Canvas, tạo nó trong lớp MIDlet và hiển thị nó trong phương thức commandAction() public Displayable initGameCanvas() {   if (gameCanvas == null){     gameCanvas = new MyCanvas();     // add a back Command to return to the menu screen     gameCanvas.addCommand(initBackCommand());     // set the listener to our actions     gameCanvas.setCommandListener(this);   }   return gameCanvas; } Vòng lặp game: Trước khi bắt đầu, bạn phải hiểu những cách thức thông thường mà một ứng dụng game hoạt động. Một game hay animation được xây dựng dựa trên một mẫu(piece) code thực thi được lặp đi lặp lại. Mẫu code này theo dõi giá trị  của các biến và các trạng thái game. Dựa trên trạng thái game,  đoạn code sẽ vẽ hay vẽ lại các phần tử trên game. Các giá trị của biến có thể được thay đổi bởi các tương tác của người dùng hay các hành vi của game bên trong. Điều này được tạo ra bằng cách đặt đoạn code đó lặp đi lặp lại trong một vòng lặp liên tục. Trước khi vào vòng lặp, một biến có thể sẽ được kiểm tra xem game còn chạy hay không. Nếu không, vòng lặp có thể được thoát. Các đoạn code trong vòng lặp nên cho phép thread thực thi hiện hành sleep vài giây để điều khiển tốc độ(rate) mỗi khi trạng thái game được cập nhật(đó là sau bao lâu thì màn hình game được refresh). Code có dạng sau: public class MyCanvas extends GameCanvas implements Runnable{ public void start() { run = true; Thread t = new Thread(this); t.start(); } public void stop() { run = false; } public void run(){ init(); while (run){ // update game elements, positions, collisions, etc.. updateGameState(); // check user input checkUserInput(); // render screen updateGameScreen(); // redraws screen flushGraphics(); // controls at which rate the updates are done Thread.sleep(10); } } } Ở đoạn code trên, lớp GameCanvas được sử dụng. Lớp GameCanvas là một trường hợp đặc biệt của lớp Canvas, được optimize cho các game. Nó sử dụng với các kỹ thuật sau: Bộ đệm đôi(double buffer): GameCanvas sử dụng một image off-screen mà image này được sử dụng cho tất cả các thao tác vẽ. Khi thao tác vẽ hoàn tất, nó được vẽ lên màn hình nhờ sử dụng phương thức flushGraphics(). Điều này giúp tránh tình trạng màn hình bị flick và các chuyển động mượt mà hơn. Lưu trữ trạng thái phím trong một mảng: Thông qua phương thức getKeyStates(), bạn có thể truy cập vào một mảng bit tương ứng với trạng thái của mỗi phím sử dụng các giá trị hằng(constant) được định nghĩa trong Canvas. Bên cạnh sử dụng GameCanvas, bạn cần sử dụng một Thread để giữ các chuyển động trong game độc lập với các event của MIDlet. Bằng cách này, các animation sẽ không cần chờ các event của hệ thống vẽ lại chính nó. Trong ví dụ này, game có 3 thực thể: Pad: là một hình chữ nhật nhỏ di chuyển từ trái sang phải nằm phái dưới màn hình. Ball: nằm trước Pad, khi bạn nhấn phím Fire, nó sẽ di chuyển với tốc độ theo chiều dọc ngang của Pad. Brick: các khối tĩnh nằm phía trên màn hình, khi chúng trúng Ball thì chúng sẽ biến mất. Mục đích của game là làm cho các Brick biến mất càng nhanh càng tốt, và không để cho Ball chạy ra khỏi phía dưới của màn hình. Game cần theo dõi 3 biến: Điểm số của người chơi. Người chơi sẽ nhận được 10 điểm mỗi lần làm một Brick biến mất. Số lượt chơi: mỗi lần Ball chạy ra khỏi phía dưới màn hình, người chơi mất một lượt. Thời gian chơi game: người chơi phải hoàn tất game trong khoảng thời gian cho phép hay giới hạn. Lớp Entity là supeclass của 3 thực thể trên, gồm có các thuộc tính và chức năng sau: x, y: Xác định vị trí hiện tại của thực thể. speedX, speedY: Xác định tốc độ của thực thể. width, height: Xác định kích thước của thực thể. update(): Xác định hành vi của các thực thể. paint(): phương thức được lớp Graphics sử dụng để vẽ các thực thể. collided(): Kiểm tra va chạm giữa các thực thể. Tiếp theo, mở rộng lớp Entity cho mỗi phần tử game và cài đặt phương thức update() và paint(). Đối với Ball: public class Ball extends Entity { public int radium = 2; public Ball(int radium){ this.radium = radium; width = radium * 2; height = radium * 2; // red color this.color = 0×00FF0000; } /** * Paints the ball using a circle */ public void paint(Graphics g) { g.setColor(color); g.fillArc(x, y, radium*2, radium*2, 0, 360); } /*** * Updates the ball position. */ public void update() { // update position oldX=x; oldY=y; x += speedX; y += speedY; } } Đối với Pad: public class Pad extends Entity{ int minLimit = 0; int maxLimit = 1; public Pad(int width, int height) { this.width = width; this.height = height; } public void paint(Graphics g) { g.setColor(0,0,255); g.fillRect(x, y, width, height); } public void update() { // change x position according the speed x += speedX; // check if world bounds are reached if (x < minLimit) { x = minLimit; } if (x+width > maxLimit){ x = maxLimit – width; } } } Đối với Brick: public class Brick extends Entity { boolean active = true; public Brick(int color){ this.color = color; } public void paint(Graphics g) { // only paints if still active if (active){ g.setColor(color); g.fillRect(x, y, width, height); } } public void update() { // the bricks don’t move } } Bây giờ, tạo và cấu hình các lớp này trên lớp Canvas. Tạo phương thức init() trên lớp Canvas: public void init(){ // resets lifes lifes = 3; // resets score score = 0; // resets time time = 0; // bricks hit bricksHit = 0; // create a pad pad = new Pad(getWidth()/10,getWidth()/10/4); pad.x = (this.getWidth()-pad.width) / 2; pad.y = this.getHeight() – (2*pad.height); pad.maxLimit = getWidth(); pad.minLimit = 0; // create ball ball = new Ball(4); ball.x = getWidth() / 2; ball.y = getHeight() / 2; ball.speedX = 1; ball.speedY = 1; // set collision limits wallMinX = 0; wallMaxX = getWidth(); wallMinY = 0; // to allow to get out of screen wallMaxY = getHeight() + 4 * ball.radium; // create bricks Brick brick; bricks = new Vector(); for (int i=0; (i*(BRICK_WIDTH+2))<getWidth(); i++){ brick = new Brick(Util.setColor(255,0,0)); brick.width  = BRICK_WIDTH; brick.height = BRICK_HEIGHT; brick.x = (i*(brick.width+2)); brick.y = 20; bricks.addElement(brick); } } Sau khi tất cả các đối tượng được tạo ra, cập nhật lại trạng thái và vẽ lại, được mô tả trong 2 phương thức updateGameState() và updateGameScreen(): // draws elements to the screen protected void updateGameScreen(Graphics g) { // stores width and height width = getWidth(); height = getHeight(); // set background color g.setColor(0,0,0); // clear screen g.fillRect(0, 0, width, height); // draw score g.setColor(255,255,255); g.drawString(“Score:”+score+” Lifes:”+lifes+” Time: “+time, 0, 0, Graphics.TOP|Graphics.LEFT); // draw game elements pad.paint(g); ball.paint(g); // draw bricks stored in the Vector bricks for (int i=0; i < bricks.size(); i++){ Brick brick = (Brick)(bricks.elementAt(i)); brick.paint(g); } } // updates state of all elements in the game public void updateGameState(){ pad.update(); ball.update(); checkBallCollisionWithWalls(); checkBallCollisionWihPad(); checkBallCollisionWithBricks(); checkBallOutOfReach(); // check if bricks ended if (bricksHit == bricks.size()){ run = false; } } Game cần tương tác trở lại với các sự kiện keypad để di chuyển pad của người chơi. // update game entities according to use presses on keypad   public void checkUserInput() {     int state = getKeyStates();     if ( (state & GameCanvas.LEFT_PRESSED) > 0) {       // move left       pad.speedX=-1;          } else if ( (state & GameCanvas.RIGHT_PRESSED) > 0) {       // move right       pad.speedX=1;     } else {       // don't move       pad.speedX=0;     }   } Bây giờ nếu bạn chạy trò chơi trên mô phỏng, các bạn sẽ có một màn hình trò chơi thực sự, nơi bạn có thể chơi trò chơi riêng của bạn: Di chuyển pad, trúng ball và xóa tất cả các brick. Trong phần tiếp theo giải thích cách sử dụng hình ảnh để có được một trò chơi tìm kiếm tốt hơn. Phát triển game đơn giãn trên Mobile(P4) Trong bài trước, lớp GameCanvas được xây dựng với tất cả các tương tác giữa các element chính. Bây giờ, tất cả các element gameplay được xây dựng, đây là lúc cải thiện giao diện trực quan của trò chơi bằng cách sử dụng các tập tin hình ảnh thay vì sử dụng phương thức draw/fill trên đối tượng graphics để biểu diễn các thực thể trò chơi. Tôi đã tạo ra một số hình ảnh dựa trên các tài nguyên SpriteLib sẽ được sử dụng trong trò chơi. Image Để truy cập và hiển thị các hình ảnh trong MIDlet, ta phải sử dụng một phần tử hay lớp Image trong giao diện người dùng cấp thấp. Lớp này lưu trữ dữ liệu hình ảnh đồ họa độc lập với thiết bị hiển thị trong một bộ nhớ đệm off-screen. Các hình ảnh hoặc ở dạng Mutable hay Immutable phụ thuộc vào cách mà chúng được tạo ra. Thông thường, các hình ảnh dạng Immutable được ta ra thông qua việc load hình ảnh từ tài nguyên như file Jar hay mạng. Các hình ảnh dạng Immutable được tạo ra thông qua việc sử dụng phương thức tĩnh createImage() của lớp Image. Tuy nhiên, một khi ảnh Immutable đã được tạo ra thì nó không thể được sửa đổi. Các hình ảnh dạng Mutable được tạo ra thông qua phương thức khởi dựng(Constructor) của lớp Image và lúc đó nó chỉ chứa các pixel trắng. Ứng dụng có thể biểu diễn image Mutable bằng cách gọi phương thức getGraphics() trên Image để có được đối tượng Graphics cho mục đích này. Trong game này, các hình ảnh Immutable đại diện cho các thực thể game. Bây giờ, ta hãy thay đổi lớp Pad và tạo ra đối tượng Image trong constructor của nó. Image image; public Pad() { try { image = Image.createImage(“/pad.png”); width = image.getWidth(); height = image.getHeight(); } catch (IOException e) { e.printStackTrace(); } } Sau đó, cài đặt lại phương thức paint() của lớp Pad để vẽ hình ảnh: public void paint(Graphics g) { g.drawImage(image, x,y, Graphics.TOP | Graphics.LEFT); } Lưu ý: khi tạo hình ảnh, bạn nên sử dụng ảnh với định dạng PNG, bởi đây là định dạng thông dụng của các loại thiết bị. Những thiết bị đời mới có thể hỗ trợ nhiều định dạng khác như: JPEG, BMP, tuy nhiên để an toàn bạn nên tạo ra file ảnh dạng PNG. Tiếp theo, ta sửa đổi lớp Ball như sau: Image image; public Ball() { try { image = Image.createImage(“/ball.png”); width = image.getWidth(); height = image.getHeight(); } catch (IOException e) { e.printStackTrace(); } } public void paint(Graphics g) { g.drawImage(image, x,y, Graphics.TOP | Graphics.LEFT); } Bây giờ, nếu bạn chạy ứng dụng, bạn sẽ cố một Ball đẹp, tuy nhiên trong game sử dụng nhiều Brick với màu sắc khác nhau, và mỗi Brick được cắt từ một Image chính. Bạn có thể làm điều này thông qua sử dụng phương thức setClip() trong lớp Graphics, tuy nhiên từ MIDP 2.0 trở đi, nó còn một hỗ trợ một lớp Sprite để ta làm điều này. Sprite Một Sprite là một thuật ngữ chung trong game. Nó tham chiếu đến một phần tử trực quan được tạo ra bởi các Image, thường chuyển động và di chuyển xung quanh các phần tử khác một cách độc lập trong game. Lớp Sprite trong MIDP 2.0 đại diện cho khái niệm này. Nó cho phép tạo ra các Sprite dựa trên các hình ảnh với nhiều frame. Nó có thể thay đổi frame, điều khiển chuyển động và kiểm tra va chạm với các phần tử khác. Tất cả các khả năng này được sử dụng trong các thực thể của game, và ta hãy xem cách xây dựng Brick: public static int BRICK_FRAMES = 20; Image image = null; Sprite sprite = null; public Brick(){ // load image image = Image.createImage(“/bricks.png”); // create the sprite with 20 frames, one for each brick sprite = new Sprite(image,image.getWidth()/BRICK_FRAMES, image.getHeight()); width = sprite.getWidth(); height = sprite.getHeight(); } Mã này tạo ra một Sprite với 20 khung hình(frame), một cho mỗi Brick có sẵn trên hình ảnh. Trước khi vẽ Brick bạn cần thay đổi frame sẽ được sử dụng. public void paint(Graphics g) { if (active){ sprite.nextFrame(); sprite.setPosition(x, y); sprite.paint(g); } } Bây giờ, nếu bạn chạy Midlet, bạn sẽ được các Brick thay đổi màu sắc vào tất cả thời gian. Tuy nhiên, vấn đề đặt ra ở đây là hình ảnh bricks.png được load cho mỗi Brick được tạo ra, điều này thật sự không tối ưu. Sửa đổi mã để nó tối ưu như sau: public Brick(Image image, int numFrames, int frameSelected){ sprite = new Sprite(image,image.getWidth()/numFrames,image.getHeight()); // set frame sprite.setFrame(frameSelected); // get size for collision detection width = sprite.getWidth(); height = sprite.getHeight(); } public void paint(Graphics g) { if (active){ sprite.setPosition(x, y); sprite.paint(g); } } Bây giờ chỉ cần load một lần trong phương thức init () và chọn một frame sẽ được sử dụng trong vòng đời của một Brick. // create bricks Image bricksFrames = null; try { bricksFrames = Image.createImage(“/bricks.png”); } catch (IOException e) { e.printStackTrace(); } Brick brick = new Brick(bricksFrames, 20, 0); bricks = new Vector(); for (int i=0; (i*(brick.width+2)) < getWidth(); i++){ brick = new Brick(bricksFrames, 20, i); brick.sprite.setFrame(i); brick.x = (i*(brick.width+2)); brick.y = 20; bricks.addElement(brick); } Bây giờ bạn có một hàng các Brick đẹp, mỗi hàng đều có mỗi Brick với một màu riêng. Trong bài tiếp theo, chúng ta sẽ thảo luận cách lưu điểm số của ngươi chơi(high-score). Phát triển game đơn giãn trên Mobile(P5) Để lưu điểm số high-score, trước tiên bạn cần hiểu Input Output trong MIDlet và cách sử dụng RecordStores. Streams Hãy bắt đầu với các operation IO đơn giãn. Các lớp mà thực thi chúng nằm trong gói java.io. Java ME chỉ có một ít các lớp sẵn có trong Java SE nhưng tất cả chúng đều rất hữu ích. Input Stream, Output Stream: Các lớp cơ sở cho các byte streams kiểu binary. ByteArrayInputStream, ByteArrayOutputStream: các mảng buffer stream trong bộ nhớ. DataInputStream, DataOutputStream: Đọc và ghi các kiểu dữ liệu nguyên thủy Java(int, float, String, ) sang streams. Reader, Writer: Các lớp cơ sở cho các stream character. OutputStreamWriter, InputStreamReader: Các lớp đọc stream character sử dụng encodings. Để học cách sử dụng các class này, ta tạo file settings để lưu trữ các options của game. Số lượt chơi(life) Tốc độ Ball Thời gian hoàn tất một level Số điểm khi ném trúng một Brick Để thực thi file Settings này, ta tạo một file text settings.txt, nơi mà mỗi dòng của nó đại diện cho một setting. Tên setting và giá trị của nó được phân cách bởi dầu ‘=’. ball_speed=2 start_lifes=4 level_time=100 brick_points=10 Thêm file này vào tài nguyên của project và đảm bảo rằng file này được lưu trong file JAR với các lớp. Để truy cập vào file này, bạn sử dụng hàm getResourceAsStream(String name) từ lớp Class. Phương thức này cho phép bạn truy cập vào bất kỳ file nào trong file JAR. Với đường dẫn root “/” tham chiếu tới root level của file JAR. Ví dụ, đoạn mã sau dùng để truy cập vào file settings.txt của file JAR: InputStream is = this.getClass().getResourceAsStream(“/settings.txt”); // read first byte byte c= is.read(); Đoạn code trên trả về byte đầu tiên của file, nhưng cái thực sự bạn cần là đọc một dòng như là một stream character. Sử dụng InputStreamReader để thay thế và phân tích mỗi dòng cho mỗi cặp giá trị . public class Settings { public Hashtable values; public Settings(String file) { values = new Hashtable(10); read(file); } public String getSetting(String key){ return (String)(values.get(key)); } /** * Opens the file and reads all the settings to the hashtable * @param file, the name of the file to read */ public void read(String file){ // open file InputStream is = this.getClass().getResourceAsStream(file); InputStreamReader isr = new InputStreamReader(is); // create a buffer to store lines StringBuffer lineBuffer = new StringBuffer(); int c; try { c = isr.read(); while(c != -1){ lineBuffer.append((char)c); c = isr.read(); // checks for end of line character if ( c == 10 || c==-1){ // cleans extra spaces or end of lines chars String line = lineBuffer.toString().trim(); // splits the string using the = character int pos = line.indexOf(“=”); if (pos != -1){ // adds a new setting String key = line.substring(0,pos); String value = line.substring(pos+1); values.put(key, value); } // clean buffer lineBuffer.setLength(0); } } } catch (IOException e) { e.printStackTrace(); } finally { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } } } Bây giờ, bạn chỉ cần sử dụng lớp này trong phương thức init() của lớp Canvas: public void initSettings(){ Settings setting = new Settings(“/settings.txt”); BALL_SPEED = Integer.parseInt(setting.getSetting(“ball_speed”)); MAX_LIFES = Integer.parseInt(setting.getSetting(“start_lifes”)); MAX_TIME = Integer.parseInt(setting.getSetting(“level_time”)); BRICK_SCORE = Integer.parseInt(setting.getSetting(“brick_points”)); } public void init(){ initSettings(); [...] } Lúc này, file cấu hình đã được tạo ra, tuy nhiên phương thức getResourceAsStream chỉ cho phép đọc file lưu trữ trong file JAR nhưng ko cho phép ghi vào chúng. RecordStore Để ghi vào tập tin, bạn cần sử dụng lớp RecordStore. Lớp này cho phép tạo một database nhỏ, nơi mà bạn có thể lưu trữ hay lấy dữ liệu liên tục mỗi khi start MIDlet. Class này được cài đặt trong gói javax.microedition.rms và nó cung cấp những phương thức  constructor tĩnh sau đây: openRecordStore(String recordStoreName, boolean createIfNecessary) openRecordStore(String recordStoreName, boolean createIfNecessary, int authmode, boolean writable) openRecordStore(String recordStoreName, String vendorName, String suiteName) Những phương pháp này cho phép tạo ra và / hoặc mở một RecordStore. Tên của các RecordStore phân biệt hoa thường và có chiều dài tối đa 32 ký tự. Một trong những tính năng đặc biệt mà cần phải được đưa vào account là setting authmode. Thiết lập này có hai giá trị: AUTHMODE_PRIVATE: trong chế độ này, chỉ có MIDlet tạo ra RecordStore mới được phép truy cập nó. AUTHMODE_ANY: Trong chế độ này, bất kỳ MIDlet nào đều có thể mở RecordStore, đọc dữ liệu từ nó và nếu setting writable thiết lập là true, thì có thể ghi dữ liệu vào nó.Điều này cho phép chia sẽ dữ liệu giữa các MIDlet, vì thế có thể 2 game cùng chia sẽ một bảng high-score. Các RecordStore được xác định duy nhất bởi tên tạo ra nó, MIDlet-Name, MIDlet-Vendor trong file JAD. Sau khi bạn mở RecordStore, bạn có thể thêm record vào nó. Mỗi record được làm bằng một mảng byte và nó có một ID duy nhất khi nó được tạo ra. Sau khi bạn tạo một record, bạn có thể lấy dữ liệu của nó, thay đổi dữ liệu, và xóa nó. Các phương thức sau đây hỗ trợ các thao tác đó: addRecord(byte[] data, int offset, int numBytes) deleteRecord(int recordId) setRecord(int recordId, byte[] newData, int offset, int numBytes) Kích thước tối đa của RecordStore phụ thuộc vào thiết bị. Bạn có thể sử dụng phương thức getSizeAvailable(), tuy nhiên giá trị trả về của phương thức này không thực sự chính xác trong hầu hết các loại thiết bị. Sử dụng RecordStore để lưu trữ high score như sau: public void saveData() { try { // open records store options RecordStore options = RecordStore.openRecordStore(“options”, true); byte[] data = saveOptions(); // check if record store not empty if (options.getNumRecords() != 0) { // update the settings options.setRecord(1, data, 0, data.length); } else { // adds the settings options.addRecord(data, 0, data.length); } // closes the record store options.closeRecordStore(); } catch (RecordStoreException ex) { } } public byte[] saveOptions() { // create a byte array stream to store data temporarily ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(baos); try { dos.writeBoolean(soundOn); // write scores for (int i =0; i < scores.length; i++){ dos.writeInt(scores[i].value); dos.writeUTF(scores[i].name); dos.writeLong(scores[i].when.getTime()); } // push all the data to the byte array stream dos.flush(); } catch (IOException ex) { } // returns bytes from stream return baos.toByteArray(); } Trong ví dụ trên, tên RecordStore là “options” được dùng để lưu trữ settings và high scores. Đối với quá trình lưu trữ, một DataOutputStream với một ByteArrayOutputStream để convert dữ liệu sang một mảng nhị phân. Bây giờ bạn cần cài đặt việc đọc dữ liệu: public void loadData() { try { RecordStore options = RecordStore.openRecordStore(“options”, true); // check if record store not empty if (options.getNumRecords() != 0) { loadOptions(options.getRecord(1)); } options.closeRecordStore(); } catch (RecordStoreException ex) { } } public void loadOptions(byte[] data) { // create a byte array stream to store data temporarily ByteArrayInputStream bais = new ByteArrayInputStream(data); // creates a data input stream to read from DataInputStream dis = new DataInputStream(bais); try { soundOn = dis.readBoolean(); // read scores for (int i = 0; i < scores.length; i++) { int value = dis.readInt(); String name = dis.readUTF(); Date date = new Date(dis.readLong()); scores[i] = new Score(value, name, date); } dis.close(); } catch (IOException ex) { } } Ở đoạn code trên, bạn chỉ cần mở một RecordStore và kiểm tra xem nó đã được tạo hay chưa. Nếu có, thì lấy dữ liệu và sử dụng DataInputStream kết hợp với ByteArrayInputStream. Bây giờ, bạn chỉ cần đưa chúng vào phần startup và shutdown MIDlet. public void initOptions() { soundOn = true; if (scores == null) { scores = new Score[10]; for (int i = 0; i < scores.length; i++) { scores[i] = new Score(0, “Empty”, new Date()); } } // loads data in record stores if available loadData(); } public void exit() { // store high scores and setting to record store saveData(); notifyDestroyed(); } Đến lúc này, bạn đã có thể lưu trữ high score và settings của player. Trong bài tiếp theo, chúng ta sẽ được tìm hiểu cách sử dụng settings và thêm vào âm thanh cho game. Phát triển game đơn giãn trên Mobile(P6) Bài viết trước mô tả làm thế nào để lưu các cài đặt trò chơi, bao gồm cả âm thanh bật / tắt màn hình, nhưng nó không có bất kỳ âm thanh nào được phát ra. Bài viết này mô tả các Java Mobile Multimedia API (MMAPI) và giải thích làm thế nào để thêm âm thanh vào game. MMAPI cung cấp một tập các khả năng đa phương tiện cho các thiết bị di động, bao gồm cả phát lại và ghi âm dữ liệu âm thanh và video từ nhiều nguồn khác nhau. Tất nhiên, không phải tất cả thiết bị di động hỗ trợ tất cả các tùy chọn này, nhưng MMAPI được thiết kế để tận dụng những khả năng có sẵn trên thiết bị và bỏ qua những cái mà nó không hỗ trợ. Thông tin MMAPI Các MMAPI được xây dựng dựa trên sự trừu tượng cấp cao của tất cả các thiết bị đa phương tiện. Sự trừu tượng này được cài đặt(implement) trong ba lớp  đã hình thành lõi(core) của các hoạt động mà bạn làm với các API này. Những lớp này là 2 interface Player và Control, và lớp Manager. Một lớp khác, lớp trừu tượng DataSource, được sử dụng để xác định vị trí tài nguyên, nhưng nếu bạn định nghĩa một phương pháp mới để đọc dữ liệu, bạn sẽ không cần sử dụng nó một cách trực tiếp. Tóm lại, lớp Manager được sử dụng để tạo ra các thể hiện Player cho các media khác nhau bằng cách xác định các thể hiện DataSource. Như vậy, các thể hiện Player có thể tạo ra được cấu hình bằng cách sử dụng các thể hiện Control. Ví dụ, hầu hết các thể hiện Player hỗ trợ VolumeControl để điều khiển âm lượng của các Player. Kiểm tra sơ đồ sau đây: Lớp Manager về cơ bản là một factory của các player được hỗ trợ bởi các phương thức sau đây: createPlayer(DataSource source): tạo một player dựa trên DataSource. createPlayer(InputStream stream, String type): tạo một player sử dụng input stream như là nguồn và giả định rằng các kiểu media đã được cung cấp. Để biết thêm các kiểu media, bạn có thể vào địa chỉ: để kiểm tra. createLayer(String url): tạo một player sử dụng một url để xác định dữ liệu nguồn. Phương thức cuối cùng cho phép bạn phân bổ các loại media khác nhau, tùy thuộc vào giao thức URL được chọn chọn. Các loại sau được hỗ trợ: Midi Player – “device://midi”: tạo a midi Player. Tone Player – “device://tone”: tạo a tone Player. Capture Audio – “capture://audio”: cho phép capture audio từ thiết bị. Capture Video – “capture://video”: cho phép capture video từ thiết bị. Capture Radio – “capture://radio?f=105.1&st=stereo”: cho phép capture radio. Để tìm hiểu các kiểu nội dung và giao thức được hỗ trợ trên thiết bị, bạn sử dụng các phương thức của lớp Manager: getSupportedContentTypes(): cung cấp một danh sách các kiểu nội dung sẵn có cho tất cả các giao thức hay một giao thức cụ thể. getSupportedProtocols(): cung cấp một danh sách các giao thức sẵn có cho tất cả các kiểu nội dung hay một kiểu nội dung cụ thể. Sau khi bạn đã tạo ra một player, bạn có thể bắt đầu sử dụng nó bằng cách đơn giản gọi phương thức start (). Khi đạt đến sự kết thúc của media thì nó stop một cách tự động. Đây là một cái nhìn đơn giản của lớp Player. Trên thực tế lớp có năm tiểu trạng thái: UNREALIZED: đây là trạng thái đầu tiên của Player thu được từ lớp Manager. REALIZED: Khi phương thức realized() được gọi, Player chuyển sang trạng thái này để thu nhận thông tin cần thiết để có được các nguồn media.Quá tình realized một Player có thể là một tài nguyên và quá trình tiêu tốn thời gian. Player có thể phải giao tiếp với server, đọc một file hay tương tác với một tập các đối tượng. PREFETCHED: Sau khi một player được realized, nó có thể vẫn cần các tài nguyên khan hiếm hay độc quyền, fill bộ đệm với dữ liệu media hay thực hiện các tiến trình start-up khác. Và điều này được thực hiện bằng cách gọi phương thức prefetch() để chuyển đổi sang trạng thái này. STARTED: Khi gọi phương thức start(), Player bắt đầu play tài nguyên media cho đến khi nó đạt đến sự kết thúc của media. CLOSED: khi gọi phương thức close(), Player chuyển đổi sang trạng thái này, giải phóng tất cả các tài nguyên nắm giữ. Và nó không thể sử dụng lại lần nữa. Hình dưới đây cho biết nhiều trạng thái khác nhau và có thể chuyển tiếp giữa chúng: Nếu ứng dụng của bạn cần thông tin về chuyển đổi trạng thái, bạn cần implements giao diện PlayerListener. Play a sound Trong game này, ý tưởng để play một sound mỗi khi Ball di chuyển chạm vào Brick hay Pad. Để làm được điều này, tạo một lớp gọi là Multimedia với phương thức playSound(): //multimedia libraries import javax.microedition.media.Manager; import javax.microedition.media.Player; import javax.microedition.media.MediaException; public class Multimedia { public void playSound(String file, String format) { try { InputStream is = getClass().getResourceAsStream(file); Player p = Manager.createPlayer(is, format); p.start(); } catch (IOException ioe) { } catch (MediaException me) { } } Bây giờ chỉ cần sử dụng phương thức bên dưới mỗi khi va chạm giữa Ball và các thực thể khác được phát hiện. Cho mục đích này, tôi đã tạo một sound có tên click.wav và nó phải sẵn có trong tài nguyên của ứng dụng. public void updateGameState(){ byte colision = ball.colided(pad); if (colision != Entity.COLLISION_NONE){ if (midlet.soundOn){ midlet.multimedia.playSound(“click.wav”, “audio/X-wav”); } } } Nếu chạy ứng dụng, bạn sẽ nghe một vài âm thanh khi play game. Capture Video Bây giờ, game đã có âm thanh, bạn có thể chụp ảnh của người chơi mỗi khi người đó đạt điểm số cao nhất. Để làm được điều này, bạn cần truy cập vào camera video và show nó vào player. Phương thức dưới sẽ chụp ảnh video và lưu nó vào một item. Player p; VideoControl vc; public Item showVideo(String url){ Item result = null; try { p = Manager.createPlayer(url); p.realize(); // Grab the video control . vc = (VideoControl)p.getControl(“VideoControl”); if (vc != null) { // create the Item with the video image result =((Item)vc.initDisplayMode(VideoControl.USE_GUI_PRIMITIVE, null)); // add a label result.setLabel(“Photo”); } // start capture p.start(); } catch (IOException ioe) { } catch (MediaException me) { } return result; } VideoControl được sử dụng để tạo Item được sử dụng trong Form: public Displayable initNewHighScore(int score, int pos) {   ...   newHighScoreForm.append(multimedia.showVideo("capture://video"));   ... } Bây giờ, sử dụng VideoControl để chụp một image từ camera. public Image captureVideo(){ Image result = null; try { // grab data byte[] imageData = vc.getSnapshot(“encoding=png”); // create image; result = Image.createImage(imageData, 0, imageData.length); } catch (MediaException me) { me.printStackTrace(); } return result; } Sau đó, gọi phương thức này khi high score đã được lưu. // we added an extra field to Score to store the image   scores[pos].image = multimedia.captureVideo(); Sau đó, show các image trong màn hình high score. Chạy ứng dụng để test chức năng mới này. Bài tiếp theo sẽ thảo luận về network trên mobile. Phát triển game đơn giãn trên Mobile(P7 – Phần cuối) Ở bài trước, các bạn đã được hướng dẫn để tạo ra một game, ở phần này chúng ta sẽ bàn đến network. Một trong các đặc điểm chính của mobile, đó là khả năng được kết nối ở mọi nơi và mọi lúc. Bên cạnh việc bạn đạt được điểm số cao(high score); kết quả trả về có thể được chia sẽ trên internet. Với JavaME, bạn có thể dễ dàng truy cập vào những đặc điểm giao tiếp sau: HTTP Socket SMS Bluetooth Bài viết này sẽ giải thích cách sử dụng giao thức HTTP để gửi dữ liệu high score đến server và làm thế nào để sử dụng chức năng gửi SMS để mời các người khác cùng tham gia game. Connector Đặc điểm chính khi sử dụng các chức năng network là lớp Connector. Lớp này cho phép tạo ra các đối tượng Connection thông qua các phương thức tĩnh: open(String name) open(String name, int mode) Tham số name chính là một chuỗi URL để xác định kết nối mà bạn muốn tạo ra. Tương ứng với giao thức ở URL là một kiểu đối tượng Connection sẽ được tạo ra. Sau đây là danh sách các URL và các đối tượng Connection tương ứng: ““: Trả về một kết nối HTTP sử dụng lớp HttpConnection. “socket://server.com:8080″: trả về một kết nối TCP/IP. “btsp://2343434d3434″: trả về một kết nối Bluetooth. “sms://+351910000000″: trả về một kết nối SMS đến một số điện thoại +351910000000. Trong các giao thức kể trên, thì giao thức HTTP được tất cả các thiết bị hỗ trợ JavaME hỗ trợ; các giao thức khác thì phụ thuộc vào thiết bị. Tuy nhiên hầu hết các loại điện thoại mới đều hỗ trợ tất cả các giao thức trên. Lưu ý rằng khi bạn cố gắng mở bất kỳ loại kết nối nào, người dùng các ứng dụng được thông báo từ hệ thống quản lý ứng dụng và cung cấp tùy chọn để chấp nhận hoặc từ chối các yêu cầu kết nối. Nếu bị từ chối yêu cầu kết nối, ứng dụng sẽ nhận được một SecurityException và kết nối không được thiết lập. HTTP Có thể nói, kết nối HTTP là một loại kết nối phổ biến nhất trong tất cả các loại kết nối. Để tạo kết nối HTTP trong J2ME, bạn làm như sau: import javax.microedition.io.Connector; import javax.microedition.io.HttpConnection; public class Network {   public byte[] httpConnection(String url, byte[] dataToSend) throws IOException   {     HttpConnection hc = null;     // Prepare Connection     hc = (HttpConnection) Connector.open(url, Connector.READ_WRITE);     [...]   } } Các kết nối HTTP được đại diện bởi lớp HttpConnection. Lớp này có ba trạng thái: Setup nơi mà các tham số yêu cầu được định nghĩa. Connected nơi mà bạn có thể gửi và nhận dữ liệu. Bạn cần gửi dữ liệu trước khi bạn có thể nhận bất kỳ sự trả lời nào. Closed sau khi bạn đọc dữ liệu và kết nối được kết thúc. Khi bạn nhận đối tượng HttpConnection từ Connector, nó rơi vào trạng thái Setup. Sau đó, bạn có thể cấu hình các tham số yêu cầu. Một trong những lựa chọn chính đó là phương thức yêu cầu HTTP(POST, GET hoặc HEAD). public byte[] httpConnection(String url, byte[] dataToSend) throws IOException { [..] if (dataToSend == null){ hc.setRequestMethod( HttpConnection.GET ); } else { hc.setRequestMethod( HttpConnection.POST ); } [...] } Bạn cũng có thể cấu hình các thuộc tính yêu cầu, chẳng hạn như kiểu nội dung mà bạn sẽ gửi trong trường hợp POST: hc.setRequestProperty("Content-type", "application/octet-stream" ); Sau khi bạn đã cấu hình kết nối, bạn có thể bắt đầu gửi dữ liệu. Khi bạn bắt đầu quá trình này, bạn không thể thay đổi các tham số yêu cầu. Nếu kết nối được thiết lập, bạn bắt đầu nhận được dữ liệu. public byte[] httpConnection(String url, byte[] dataToSend) throws IOException { [...] if (dataToSend != null){ // Write Data OutputStream os = hc.openOutputStream(); os.write( dataToSend ); os.close(); } // gets answer from  server int rc = hc.getResponseCode(); // check http response if (rc == HttpConnection.HTTP_OK){ // Read Data InputStream in = hc.openInputStream(); ByteArrayOutputStream tmp = new ByteArrayOutputStream(); int ch; while ((ch = in.read()) != -1) { tmp.write(ch); } data = tmp.toByteArray(); in.close(); hc.close(); } return data; } Xây dựng phương thức httpConnection() coi như đã xong, tiếp theo nó được cài đặt trong game để gửi high score đến server. Bạn hãy thêm một command vào màn hình high score: public Displayable initScoreForm() {   [...]   cmdSendHighScore = new Command("Send to Server", Command.ITEM, 1);   highScoreForm.addCommand(cmdSendHighScore);   [...] } Tiếp theo, bạn tạo một phương thức sử dụng nó khi Command được lựa chọn: public String sendScore(String user, int score) { String result = “No Answer”; // server to send data String url = “”; // prepare http request String urlTotal = url + “?user=” + user + “&score=” + score; byte[] data = null; try { data = network.httpConnection(urlTotal, null); } catch (IOException e) { result = “Communication Problems”; e.printStackTrace(); } catch (SecurityException s) { // user denied access to communication result = “You need to allow communications in order to send the highscore to server.”; s.printStackTrace(); } // check data return. if (data != null) { result = new String(data); } return result; } Và gọi nó khi Command được sử dụng: if (cmd == comInviteSend) {         result = sendScore(scores[0].name, scores[0].value);         display(new Alert("Result", result, null, AlertType.INFO));    } Nếu bạn sử dụng đoạn code này, bạn sẽ nhận được một cảnh báo: Warning: To avoid potential deadlock, operations that may block, such as networking, should be performed in a different thread than the commandAction() handler. Hãy nhớ rằng bạn đang thực thi đoạn mã này như là một câu trả lời cho một sự kiện mà người sử dụng đang được thực thi trên thread UI. Nếu đoạn mã này block hoặc mất một thời gian dài để thực hiện, ứng dụng sẽ gặp khó khăn. Để tránh vấn đề này, bạn nên sử dụng một thread để làm các công việc liên quan đến network. Hãy bắt đầu khai báo một thread và các biến trạng thái để điều khiển các hoạt động của thread. private static final int ACTION_SEND_HIGHSCORE = 0; public Thread workThread; public boolean active = false; Bất cứ lúc nào có một sự kiện làm tốn nhiều thời gian(như việc kết nối mạng có thể thành công hay thất bại, điều này phụ thuộc các yêu tố bên ngoài như đường truyền mạng), thì bạn nên sử dụng một thread chuyên biệt để kích hoạt các hoạt động này. public void doAction(int action) { // stores action to do this.action = action; // check if thread is already created if (workThread == null) { workThread = new Thread(this); workThread.start(); active = true; } // wakes up thread to work synchronized (this) { notify(); } } Thread này sẽ thực thi phương thức run(): public void run() { while (active) { // check what action to do switch (action) { case (ACTION_SEND_HIGHSCORE): // send the first score to the server result = sendScore(scores[0].name, scores[0].value); commandAction(cmdReceiveHighScore, highScoreForm); break; } // waits for action to do. synchronized (this) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } Để start thread này, cần khai báo một giao diện Runnable trong MIDlet: public class MyMidlet extends MIDlet implements CommandListener, Runnable { Bây giờ, bạn có thể gọi phương thức doAction() khi người dùng chọn command ‘Send high score’. Lưu ý rằng, phương thức run() gọi một command cmdReceiveHighScore. Điều này được sử dụng để hiển thị các kết quả của các giao tiếp với người dùng.  if (cmd == cmdSendHighScore) {         doAction(ACTION_SEND_HIGHSCORE);       }       if (cmd == cmdReceiveHighScore) {         display(new Alert("Result", result, null, AlertType.INFO));       } SMS Một trường hợp sử dụng cho giao tiếp trong Java ME là để mời bạn bè tham gia chơi game. Bạn cũng có thể có một cuộc cạnh tranh trong bảng điểm số cao. Thứ nhất, cài đặt phương thức gửi tin nhắn SMS trong lớp network. public boolean sendSms(String number, String message){ boolean result = true; try { //sets address to send message String addr = “sms://”+number; // opens connection MessageConnection conn = (MessageConnection) Connector.open(addr); // prepares text message TextMessage msg = (TextMessage)conn.newMessage(MessageConnection.TEXT_MESSAGE); //set text msg.setPayloadText(message); // send message conn.send(msg); } catch (Exception e) { result = false; } return result; } Từ đoạn code ví dụ trên, bạn chỉ cần sử dụng phương thức open() của lớp Connector với định dạng URL là “sms://number”. Sau đó tạo một TextMessage và gửi nó thông qua Connection. Kích thước của message được giới hạn trong 160 ký tự. Tiếp theo, bạn tạo một form để nhận tên và số điện thoại từ người dùng như sau: public Displayable initInviteForm() { if (inviteForm == null) { inviteForm = new Form(“Invite”); inviteForm.setCommandListener(this); inviteForm.addCommand(initBackCommand()); inviteName = new TextField(“Name:”, “”, 20, TextField.ANY); inviteNumber = new TextField(“Number:”, “”, 20, TextField.PHONENUMBER); inviteForm.append(inviteName); inviteForm.append(inviteNumber); comInviteSend = new Command(“Invite”, Command.ITEM, 1); inviteForm.addCommand(comInviteSend); } return inviteForm; } Tiếp theo, trong phương thức commandAction(), bạn cài đặt code để hiển thị form và gửi message: public void commandAction(Command cmd, Displayable display) {    [...]    else if (display == inviteForm) {       if (cmd == comBack) {         display(initMainForm());       }       if (cmd == comInviteSend) {         doAction(ACTION_SEND_INVITE);       }     }   [...] } Cuối cùng, trong phương thức run(), bạn gọi phương thức sendSms() để gửi message: public void run() { [...] case (ACTION_SEND_INVITE): // invite another player to play String inviteMessage = ” invites you to play Transnoid!”; network.sendSms(inviteNumber.getString(), inviteName.getString() + inviteMessage); break; } [..] }

Các file đính kèm theo tài liệu này:

  • docxphat_trien_game_don_gian_tren_8581.docx
Tài liệu liên quan