Chủ Nhật, 2 tháng 9, 2012

Kiểm soát nhập kí tự - hay nhập tên nhân vật

Game của bạn sẽ hay hơn nếu như nó cho phép người chơi nhập tên nhân vật. Việc này làm họ thấy họ là một phần trong trò chơi. Sau đó, tên của người chơi lại xuất hiện trong các đoạn hội thoại khiến họ cảm thấy như đang được nói chuyện. Tiếp nữa, họ có thể lưu điểm số với tên của mình làm bằng chứng chiến thắng...

Những thứ trên thế nào? Bạn có thể làm với textbox dễ dàng? No, my young padawan =))) . Đúng, bạn có thể làm vậy nhưng game bạn sẽ bị ẩn đi khi textbox xuất hiện. Khi đó nó sẽ dùng theme của điện thoại và trông không giống như textbox đó là một phần của game. Lại một lần nữa, đây là vấn đề về thẩm mỹ.

Chúng ta dùng code của bài này để hiển thị chữ bitmap. Nếu bạn chưa học thì hãy bắt đầu bài đó trước. Hay nếu không thì bạn cũng có thể dùng phương thức drawChar( ) của Graphics thay vì của clsFont chúng ta tự định nghĩa.

Thiếu phím bấm

Trong bài này chúng ta viết chữ như những game arcade console ngày xưa. Chúng chỉ có phím điều khiển 4 chiều, 6 phím chức năng, 1 nút start và 1 nút reset nhưng chúng vẫn có thể cho người chơi nhập tên. Còn chiếc điện thoại chúng ta đang có dù nhiều phím bấm hơn nhưng không phải máy nào cũng có mã phím giống nhau.

Rất may là lớp GameCanvas cho chúng ta các phím cơ bản giống nhau ở hầu hết các loại máy điện thoại:
  • Phím 2 - Nút UP
  • Phím 8 - Nút DOWN
  • Phím 4 - Nút LEFT
  • Phím 6 - Nút RIGHT

Chúng ta sẽ làm như sau:

  • LEFT - Chọn kí tự bên trái 
  • RIGHT - Chọn kí tự bên phải
  • UP - Cuộn kí tự lên trên
  • DOWN - Cuộn kí tự xuống dưới
  • FIRE - Chấp nhận thay đổi


Các giá trị

Bắt đầu sửa lớp clsCanvas. Thêm các biến toàn cục sau vào lớp này:
private clsFont myFont;

// this will hold the resulting text
private String prText = "AAAAAA";

// the currently selected character
private int prSelected = 0;

// width and height of the characters
private int prWidth = 9;
private int prHeight = 10;

// character spacing - includes width of character
private int prSpacing = 12;

// vars for timing the blinking cursor
private long prStart = 0;
private long prDelay = 100;
private boolean prShow = true;
    /** Creates a new instance of clsCanvas */
    public clsCanvas(midMain m) {

Biến String prText chứa đoạn text người chơi sẽ nhập vào và đồng thời phản hồi các kí tự người chơi chọn. Độ dài chuỗi gán cho prText cũng là độ dài tối đa lí tự có thể nhập và là các kí tự xuất hiện mặc định.

Biến nguyên prSelected giữ chỉ số của kí tự đang được chọn từ chuỗi prText.

Các biến prWidth và prHeight giữ chiều dài và chiều cao của kí tự. Chúng cũng được dùng trong clsCanvas.drawChar( ) để resize khung cắt.

Biến nguyên prSpacing xác định khoảng cách tính theo pixel giữa các kí tự. Giá trị được gán cho psSpacing là tổng độ rộng các kí tự và khoảng cách giữa chúng.

Thay thế một kí tự đơn

Tiếp theo, chúng ta viết một phương thức là replaceCharAt( ) cho phép chúng ta thay đổi một kí tự đơn lẻ trong chuỗi cho trước.

    public String replaceCharAt(String s, int pos, char c) {
       if (pos < (s.length() - 1)){
          return s.substring(0, pos) + c + s.substring(pos + 1);
       } else {
          return s.substring(0, pos) + c;
       }
    }    
    public void run() {

Phương thức này có 3 tham số:

  • String s - chuỗi đã cho
  • int pos - vị trí kí tự được thay đổi
  • Char c - kí tự thay thế

Người chơi điều khiển

Chúng ta cũng cần phương thức nhận và phản hồi phím người dùng nhấn và chuyển đổi hiển thị con trỏ khi cần thiết. Thêm phương thức updatePompt( ) như sau

    public void updatePrompt(long currTick){
        // get text length
        int len = prText.length();
        // get current selected characters ASCII value
        int prOrd = (int)prText.charAt(prSelected);
        
        // is it time to change the cursors visibility
        if ((currTick - prStart) >= prDelay){
            // update starting time
            prStart = currTick;
            // toggle cursor visibility
            prShow = !prShow;
        }
        
        if (isDown[leftKey]){
            // if not first character
            if (prSelected > 0){
                // select previous character in string
                prSelected--;
            }
        } else if (isDown[rightKey]){
            // if not last character
            if (prSelected < (len - 1)){
                // select next character in string
                prSelected++;
            }
        } else if (isDown[upKey]){
            if (prOrd == 97){ // small leter a
                prOrd = 90; // jump to capital letter Z
            } else if (prOrd == 65){ // capital letter A
                prOrd = 32; // jump to space character
            } else if (prOrd == 32){ // space character
                prOrd = 122; // jump to small leter z
            } else if ((prOrd > 97) || (prOrd > 65)){
                prOrd--; // previous character
            }
            // replace the selected character with the new character
            prText = replaceCharAt(prText, prSelected, (char)prOrd);
        } else if (isDown[downKey]){
            if (prOrd == 32){  // space character
                prOrd = 65; // jump to capilat letter A
            } else if (prOrd == 90){ // capital letter Z
                prOrd = 97; // jump to small letter a
            } else if (prOrd == 122){ // small letter z
                prOrd = 32; // jump to space character
            } else if ((prOrd < 90) || (prOrd < 122)){
                prOrd++; // next character
            }
            // replace the selected character with the new character
            prText = replaceCharAt(prText, prSelected, (char)prOrd);
        }
    }
    public void run() {

Phương thức này sử dụng giá trị Long currTick là thời gian hiện tại tính bằng milli giây. Giá trị này dùng kiểm tra xem thời gian qua giữa prStart và currTick có lớn hơn khoảng thời gian delay prDelay hay không. Nếu có, biến boolean prShow sẽ điều khiển con trỏ ẩn hay hiện qua giá trị true/false.

Chú ý rằng phương thức updatePrompt( ) chấp nhận chữ hoa, chữ nhỏ, và dấu cách. Không khó để thêm số vào hay thậm chí là toàn bộ các kí tự. Nhưng trong trường hợp này chỉ có chữ hoa.

Bạn cũng sẽ muốn giữ kí tự "dấu cách", vì người chơi có thể dùng nó khi tên họ ngắn mà kí tự bạn cho quá dài. Bằng cách này bạn có thể làm gọn các khoảng trống từ giá trị của prText khi người chơi đã nhập tên xong.

Hiển thị kí tự

Cuối cùng chúng ta viết phương thức drawPrompt( ) hiện chữ và con trỏ.

    public void drawPrompt(Graphics g, int x, int y){
        // get length of the string
        int len = prText.length();
        
        // loop through the characters
        for (int i = 0; i < len; i++){
            // get ASCII value
            int cIndex = (int)prText.charAt(i);
            
            // compute next drawing position - X 
            int cx = x + (i * prSpacing);
            
            // draw the character
            myFont.drawChar(g, cIndex, cx, y, prWidth, prHeight);
            
            // compute cusor position - Y
            int cy = y + 6;

            // if current char is selected
            if (i == prSelected){
                // if cursor shoud be visible
                if (prShow) {
                    //draw the cursor using char 95 or underscore character
                    myFont.drawChar(g, 95, cx, cy, prWidth, prHeight);
                }
            } else {
                //draw the cursor using char 95 or underscore character
                myFont.drawChar(g, 95, cx, cy, prWidth, prHeight);
            }
        }
    }
    public void run() {

Phương thức này vẽ từng chữ trong prText và gạch dưới mỗi chữ. Con trỏ nhấp nháy dưới chữ đang được chọn.

Phương thức này có 3 tham số:
  • Graphics g - Đối tượng Graphics dùng vẽ chữ.
  • int x - Tọa độ x bắt đầu vẽ
  • int y - Tọa độ y bắt đầu vẽ

Thực hiện

Hãy chỉnh sửa code phương thức run( ) để dùng các phương thức vừa viết. Đầu tiên, lời gọi phương thức updatePrompt( ) bên dưới lời gọi checkKey( )
           checkKeys(iKey, lCurrTick);
           
           updatePrompt(lCurrTick);           
           if (isDown[fireKey]){

Sau đó sử dụng:
           g.fillRect(0, 0, screenW, screenH);
           
           myFont.drawString(g, "Enter your name:", 10, 20);

           drawPrompt(g, 10, 40);

           myFont.drawString(g, "LEFT/RIGHT - move cursor", 10, 80);
           myFont.drawString(g, "UP/DOWN - change letter", 10, 100);

           myFont.drawString(g, "Hello " + prText.trim() + "!", 10, 140);           
           flushGraphics();

...và chạy thử để xem kết quả:
User input sample output.

Cuối cùng là lớp clsCanvas hoàn chỉnh
package MyGame;

import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.game.GameCanvas;

public class clsCanvas extends GameCanvas implements Runnable {
// key repeat rate in milliseconds
public static final int keyDelay = 250;    

//key constants
public static final int upKey = 0;
public static final int leftKey = 1;
public static final int downKey = 2;
public static final int rightKey = 3;
public static final int fireKey = 4;

//key states for up, left, down, right, and fire key
private boolean[] isDown = {
    false, false, false, false, false
};
//last time the key changed state
private long[] keyTick = {
    0, 0, 0, 0, 0
};
//lookup table for key constants :P
private int[] keyValue = {
    GameCanvas.UP_PRESSED, GameCanvas.LEFT_PRESSED,
    GameCanvas.DOWN_PRESSED, GameCanvas.RIGHT_PRESSED, 
    GameCanvas.FIRE_PRESSED
};

private boolean isRunning = true;    
private Graphics g;
private midMain fParent;

private clsFont myFont;

// this will hold the resulting text
private String prText = "AAAAAA";

// the currently selected character
private int prSelected = 0;

// width and height of the characters
private int prWidth = 9;
private int prHeight = 10;

// character spacing - includes width of character
private int prSpacing = 12;

// vars for timing the blinking cursor
private long prStart = 0;
private long prDelay = 100;
private boolean prShow = true;
    /** Creates a new instance of clsCanvas */
    public clsCanvas(midMain m) {
        super(true);
        fParent = m;
        setFullScreenMode(true);
    }
    
    public void start(){
        Thread runner = new Thread(this);
        runner.start();
    }
    
    public void load(){
        try{
            // load the images here
        }catch(Exception ex){
            // exit the app if it fails to load the image
            isRunning = false;
            return;
        }
        
        myFont = new clsFont();
        myFont.load("/images/fonts.png");        
    }
  
    public void unload(){
        // make sure the object gets destroyed
        myFont.unload();
        myFont = null;
    }    
    
    public void checkKeys(int iKey, long currTick){
        long elapsedTick = 0;
        //loop through the keys
        for (int i = 0; i < 5; i++){ 
            // by default, key not pressed by user
            isDown[i] = false;
            // is user pressing the key
            if ((iKey & keyValue[i]) != 0){ 
                elapsedTick = currTick - keyTick[i];
                //is it time to toggle key state?
                if (elapsedTick >= keyDelay){ 
                    // save the current time
                    keyTick[i] = currTick;  
                    // toggle the state to down or pressed
                    isDown[i] = true; 
                }
            }
        }
    }
    
    public String replaceCharAt(String s, int pos, char c) {
       if (pos < (s.length() - 1)){
          return s.substring(0, pos) + c + s.substring(pos + 1);
       } else {
          return s.substring(0, pos) + c;
       }
    }    
       
    public void updatePrompt(long currTick){
        // get text length
        int len = prText.length();
        // get current selected characters ASCII value
        int prOrd = (int)prText.charAt(prSelected);
        
        // is it time to change to cursors visibility
        if ((currTick - prStart) >= prDelay){
            // update starting time
            prStart = currTick;
            // toggle cursor visibility
            prShow = !prShow;
        }
        
        if (isDown[leftKey]){
            // if not first character
            if (prSelected > 0){
                // select previous character in string
                prSelected--;
            }
        } else if (isDown[rightKey]){
            // if not last character
            if (prSelected < (len - 1)){
                // select next character in string
                prSelected++;
            }
        } else if (isDown[upKey]){
            if (prOrd == 97){ // small leter a
                prOrd = 90; // jump to capital letter Z
            } else if (prOrd == 65){ // capital letter A
                prOrd = 32; // jump to space character
            } else if (prOrd == 32){ // space character
                prOrd = 122; // jump to small leter z
            } else if ((prOrd > 97) || (prOrd > 65)){
                prOrd--; // previous character
            }
            // replace the selected character with the new character
            prText = replaceCharAt(prText, prSelected, (char)prOrd);
        } else if (isDown[downKey]){
            if (prOrd == 32){  // space character
                prOrd = 65; // jump to capilat letter A
            } else if (prOrd == 90){ // capital letter Z
                prOrd = 97; // jump to small letter a
            } else if (prOrd == 122){ // small letter z
                prOrd = 32; // jump to space character
            } else if ((prOrd < 90) || (prOrd < 122)){
                prOrd++; // next character
            }
            // replace the selected character with the new character
            prText = replaceCharAt(prText, prSelected, (char)prOrd);
        }
    }
    
    public void drawPrompt(Graphics g, int x, int y){
        // get length of the string
        int len = prText.length();
        
        // loop through the characters
        for (int i = 0; i < len; i++){
            // get ASCII value
            int cIndex = (int)prText.charAt(i);
            
            // compute next drawing position - X 
            int cx = x + (i * prSpacing);
            
            // draw the character
            myFont.drawChar(g, cIndex, cx, y, prWidth, prHeight);
            
            // compute cusor position - Y
            int cy = y + 6;

            // if current char is selected
            if (i == prSelected){
                // if cursor shoud be visible
                if (prShow) {
                    //draw the cursor using char 95 or underscore character
                    myFont.drawChar(g, 95, cx, cy, prWidth, prHeight);
                }
            } else {
                //draw the cursor using char 95 or underscore character
                myFont.drawChar(g, 95, cx, cy, prWidth, prHeight);
            }
        }
    }
    public void run() {
       int iKey = 0;
       int screenW = getWidth();
       int screenH = getHeight();
       long lCurrTick = 0; // current system time in milliseconds;
       
       String sName = "";
       
       load();
       g = getGraphics();
       while(isRunning){
           
           lCurrTick = System.currentTimeMillis();
           iKey = getKeyStates();

           checkKeys(iKey, lCurrTick);

           updatePrompt(lCurrTick);           
           if (isDown[fireKey]){
               isRunning = false;    
           }
           
           //restore the clipping rectangle to full screen
           g.setClip(0, 0, screenW, screenH);
           //set drawing color to black
           g.setColor(0x000000);
           //fill the screen with blackness
           g.fillRect(0, 0, screenW, screenH);
           
           myFont.drawString(g, "Enter your name:", 10, 20);

           drawPrompt(g, 10, 40);

           myFont.drawString(g, "LEFT/RIGHT - move cursor", 10, 80);
           myFont.drawString(g, "UP/DOWN - change letter", 10, 100);

           myFont.drawString(g, "Hello " + prText.trim() + "!", 10, 140);           
           flushGraphics();
           
           try{
               Thread.sleep(30);
           } catch (Exception ex){
               
           }
       }
       g = null;
       unload();
       fParent.destroyApp(false);
       fParent = null;
    }
}

Đến đây là hết các bài học của Devlin.


Chúc các bạn thành công !!

Không có nhận xét nào:

Đăng nhận xét