Thứ Năm, 21 tháng 3, 2013

Thoại dẫn truyện


Diều quan trọng nhất trong game RPG chính là cốt truyện, như vậy thì không thể nào mà không có lời thoại nào, trong bài này chúng ta sẽ tạo ra một loạt những nhân vật dẫn thoại và cho họ tham gia vào trò chơi.
Chúng ta tạo ra một class mới gọi là trail.java như sau :
trail.java
C:\Users\thanhliem\Documents\NetBeansProjects\RPG\src\trail.java


import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;

public class trail {
    //vị trí của những nhân vật dẫn chuyện.
int[][] pos={{2,3},{12,23},{19,6}};
//mỗi nhân vật dẫn chuyện nói một câu.
String[] say={"hello world, this is role playing game","i am holyeyed, nice to meet you","in the end of world, we will found out how love is"};
//biến xác định xem đã có đang nói hay không.
boolean load=false;
//câu nói của nhân vật đang nói.
String lstr;
//vị trí của nhân vật đang nói.
int lpos=-1;
//hình ảnh vẽ nhân vật dẫn chuyện.
Image im;
//biến thời gian mốc load lời thoại.
long lload=0;
public trail(){
try{
im=Image.createImage("/trail.png");}catch(Exception e){}}
//hàm paint này sẽ vẽ lên map nhưng nhân vật dẫn chuyện.
public void paint(Graphics g,map m,int k){
    //mỗi lần lập lại cho nhân vật đang nói là -1, tức là không ai đang nói.
    lpos=-1;
    //dùng for để vẽ ra từng nhân vật trong mảng, đồng thời so sánh xem nhân vật nào đang ở gần, để gán là nhân vật hiện hành.
for(int i=0;i<pos.length;i++){
g.drawRegion(im,i*16,0,16,16,0,pos[i][0]*16+m.x,pos[i][1]*16+m.y,0);
//tính vị khoảng cách giữ nhân vật chính và nhân vật dẫn.
int nx=Math.abs(pos[i][0]*16+m.x-m.w/2),ny=Math.abs(pos[i][1]*16+m.y-m.h/2);
//nếu cách nhau 1 ô, <=16 thì gán vào.
if(nx<=16&&ny<=16)lpos=i;
}
//nếu phím bấm là -5 thì xét tiếp.
if(k==-5){
    //nếu như cách mốc thời gian là 150millis thì cho thực hiện kiểm tra.
    if(lload<System.currentTimeMillis()-150){
        //nếu gần nhân vật dẫn lpos!=-1 và load =false thì cho load.
if(!load&&lpos!=-1){
load=true;
lstr=say[lpos];}
//ngược thì thì bỏ load.
else{
load=false;}
//gán mốc thời gian load và unload.
    lload=System.currentTimeMillis();}}
//nếu như đã load thì vẽ câu nói ra màn hình.
if(load){
g.setColor(0x0000ff);
g.fillRect(0,0,m.w,m.h/5);
g.setColor(0xf0f0f0);
g.drawString(lstr,0,0,Graphics.TOP|Graphics.LEFT);}
}
}


Và chúng ta sẽ sử dụng chúng trong file map.java như thế này, ở đây tôi dùng ảnh này để vẽ nhân vật dẫn chuyện :
map.java
C:\Users\thanhliem\Documents\NetBeansProjects\RPG\src\map.java

import java.io.*;
import javax.microedition.lcdui.game.*;
import javax.microedition.lcdui.*;

public class map {
    //các thông số gồm có số cột, số hàng trong map, kích thước mỗi tile tw, th.
    //tọa độ map (x,y), phím bấm k, số lần di chuyển nhỏ.
    int mw,mh,tw,th,x,y,k,step=0,w,h;
    //chứa dữ liệu các tile trong map.
    byte[] map;
    //hình ảnh dùng làm tile
    Image tiles,other;
    //dùng class TiledLayer có sẵn trong GameCanvas để tạo map.
    TiledLayer tl;
    //mốc thời gian di chuyển lần cuối
    long lm=0;
    //thêm một biến other ot dùng làm nhân vật phụ.
        other[] ot;
        //thêm biến vẽ nhân vật dẫn thoại.
        trail tr=new trail();
    //hàm khởi tạo sẽ tải hình ảnh làm tile vào class map.
    public map(String im){
    try{
    this.tiles=Image.createImage("/"+im);
    }catch(Exception e){}}
    //hàm load dùng tải file nguồn và phân tích các thông số trong đó vào map.
    //bây giờ ta có thông số xác định tọa độ ban đầu nhân vật, tại cột c, dòng r và so sánh với độ dài rộng màn hình.
public void load(String fn,int c,int r,int w,int h){
    //file nguồn là dữ liệu với cấu trúc: [số cột]-[số hàng]-[tile width]-[tile height]-[dữ liệu map (số cột * số hàng)]
try{
InputStream is=getClass().getResourceAsStream("/"+fn);
mw=is.read();
mh=is.read();
tw=is.read();
th=is.read();
map=new byte[is.available()];
is.read(map);
//tạo TiledLayer tl từ những gì đã nhận được.
tl=new TiledLayer(mw,mh,tiles,tw,th);
//lát gạch vào TiledLayer tl.
for(int i=0;i<mh;i++){
for(int j=0;j<mw;j++){
tl.setCell(j, i, map[i*mw+j]);
}}
this.w=w;
this.h=h;
x=w/2-c*tw;
y=h/2-r*th;
//tạo file ảnh cho nhân vật phụ và khởi tạo ot ở vị trí cột 4 và hàng 6 trên map.
other=Image.createImage("/other.png");
ot=new other[3];
for(int i=0;i<ot.length;i++)
{ot[i]=new other(other,(i+1)*12*16,(i+1)*13*16);
Thread.sleep(50);}
}catch(Exception e){}}
//hàm paint dùng vẽ tl lên Graphics g, và nhận giá trị phím bấm vào hàm move.
void paint(Graphics g,int k){
    move(k);
    g.setColor(0);
    g.drawRect(x,y,tl.getWidth(),tl.getHeight());
tl.paint(g);
//vẽ nhân vật phụ lên map.
for(int i=0;i<ot.length;i++)
ot[i].paint(g,this);
//vẽ nhân vật dẫn chuyện ra màn hình.
tr.paint(g,this,k);
}
//hàm move nhận giá trị phím bấm k và kiểm tra điều kiện để thay đổi tọa độ của map.
void move(int k){
    //do mỗi lần di chuyển ta cho chuyển 16 pixel, nên ta cần chia ra 4 lần di chuyễn nhỏ để hình ảnh mượt hơn.
    //nếu thấy không cần bước di chuyển nhỏ nào nữa thì cho di gán di chuyển tiếp
if(step==0){
    //gán tọa độ cũ vào các biến gx gy.
    int gx=x,gy=y;
    //tính tọa độ mới.
    switch(k){
        case -1:
            gy+=16;
    break;
    case -2:
            gy-=16;
    break;
    case -3:
            gx+=16;
    break;
    case -4:
            gx-=16;
    break;}
    //kiểm tra xem toa6 độ mới có chạm tường không.
if(check(w/2-gx,h/2-gy)){
this.k=k;
step=4;}
}else{
    //nếu còn lượt di chuyển nhỏ step!=0, thì xét đến thời gian di chuyển lần cuối.
    if(lm<=curr()-30){
    step--;
    switch(this.k){
        case -1:
            y+=th/4;
    break;
        case -2:
            y-=th/4;
    break;
        case -3:
            x+=tw/4;
    break;
        case -4:
            x-=tw/4;
    break;}
    lm=curr();}
}
//gán vị trí cho tl.
tl.setPosition(x, y);
}
  long curr(){return System.currentTimeMillis();}
  //hàm kiểm tra xem có chạm tường không, tường là gạch số 3.
  boolean check(int a,int b){
 return tl.getCell(a/16, b/16)<3;}
}

Kết quả :


Vậy thôi, dù sao thì cũng đơn giản, câu thoại là câu đơn và chưa có hàm vẽ để tự động ngắt dòng. Chúng ta tiếp tục thêm vào trail.java như sau :
trail.java
C:\Users\thanhliem\Documents\NetBeansProjects\RPG\src\trail.java


import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;

public class trail {
    //vị trí của những nhân vật dẫn chuyện.
int[][] pos={{2,3},{12,23},{19,6}};
//mỗi nhân vật dẫn chuyện nói một câu.
String[] say={"hello world, this is role playing game","i am holyeyed, nice to meet you, if you want to follow me, please visit me in http://holyeyed.99k.org, or http://j2medraw.blogspot.com. nice to see you there....","in the end of world, we will found out how love is"};
//biến xác định xem đã có đang nói hay không.
boolean load=false;
//câu nói của nhân vật đang nói.
String lstr;
//vị trí của nhân vật đang nói.
int lpos=-1;
//hình ảnh vẽ nhân vật dẫn chuyện.
Image im;
//biến thời gian mốc load lời thoại.
long lload=0;
public trail(){
try{
im=Image.createImage("/trail.png");}catch(Exception e){}}
//hàm paint này sẽ vẽ lên map nhưng nhân vật dẫn chuyện.
public void paint(Graphics g,map m,int k){
    //mỗi lần lập lại cho nhân vật đang nói là -1, tức là không ai đang nói.
    lpos=-1;
    //dùng for để vẽ ra từng nhân vật trong mảng, đồng thời so sánh xem nhân vật nào đang ở gần, để gán là nhân vật hiện hành.
for(int i=0;i<pos.length;i++){
g.drawRegion(im,i*16,0,16,16,0,pos[i][0]*16+m.x,pos[i][1]*16+m.y,0);
//tính vị khoảng cách giữ nhân vật chính và nhân vật dẫn.
int nx=Math.abs(pos[i][0]*16+m.x-m.w/2),ny=Math.abs(pos[i][1]*16+m.y-m.h/2);
//nếu cách nhau 1 ô, <=16 thì gán vào.
if(nx<=16&&ny<=16)lpos=i;
}
//nếu phím bấm là -5 thì xét tiếp.
if(k==-5){
    //nếu như cách mốc thời gian là 150millis thì cho thực hiện kiểm tra.
    if(lload<System.currentTimeMillis()-150){
        //nếu gần nhân vật dẫn lpos!=-1 và load =false thì cho load.
if(!load&&lpos!=-1){
load=true;
lstr=say[lpos];}
//ngược thì thì bỏ load.
else{
load=false;}
//gán mốc thời gian load và unload.
    lload=System.currentTimeMillis();}}
//nếu như đã load thì vẽ câu nói ra màn hình.
if(load){
g.setColor(0x0000ff);
g.fillRect(0,0,m.w,m.h/5);
g.setColor(0xf0f0f0);
wrap(g,lstr,0,0,m.w,m.h);}
}
//hàm dùng để chia str thành nhiều đoạn nhỏ cho vừa màn hình.
public void wrap(Graphics g,String lstr,int x,int y,int w,int h){
Font f=g.getFont();
int fh=f.getHeight();
if(!lstr.endsWith(" "))lstr+=" ";
int sa=0,ns=lstr.indexOf(" ",sa),line=0;
while(ns!=-1){
    String dstr=lstr.substring(sa,ns+1);
int d=f.stringWidth(dstr);
if(d>=w-f.stringWidth("ww") ||ns==lstr.length()-1){
g.drawString(dstr,x,y+fh*line,Graphics.TOP|Graphics.LEFT);
line++;
sa=ns+1;}
ns=lstr.indexOf(" ",ns+1);}}
}

Kết quả như sau :




Thứ Bảy, 9 tháng 3, 2013

Giới hạn di chuyển

bài này không nói gì nhiều, chỉ có những chú thích trong file source. Trong ví dụ, đã xuất hiện được 3 nhân vật thông qua mảng other, và thêm điều hiện để kiểm tra xem có chạm tường hay không, nếu chạm thì không đi được nữa thế thôi. File map.java map.java
C:\Users\thanhliem\Documents\NetBeansProjects\RPG\src\map.java
import java.io.*;
import javax.microedition.lcdui.game.*;
import javax.microedition.lcdui.*;

public class map {
    //các thông số gồm có số cột, số hàng trong map, kích thước mỗi tile tw, th.
    //tọa độ map (x,y), phím bấm k, số lần di chuyển nhỏ.
    int mw,mh,tw,th,x,y,k,step=0,w,h;
    //chứa dữ liệu các tile trong map.
    byte[] map;
    //hình ảnh dùng làm tile
    Image tiles,other;
    //dùng class TiledLayer có sẵn trong GameCanvas để tạo map.
    TiledLayer tl;
    //mốc thời gian di chuyển lần cuối
    long lm=0;
    //thêm một biến other ot dùng làm nhân vật phụ.
        other[] ot;
    //hàm khởi tạo sẽ tải hình ảnh làm tile vào class map.
    public map(String im){
    try{
    this.tiles=Image.createImage("/"+im);
    }catch(Exception e){}}
    //hàm load dùng tải file nguồn và phân tích các thông số trong đó vào map.
    //bây giờ ta có thông số xác định tọa độ ban đầu nhân vật, tại cột c, dòng r và so sánh với độ dài rộng màn hình.
public void load(String fn,int c,int r,int w,int h){
    //file nguồn là dữ liệu với cấu trúc: [số cột]-[số hàng]-[tile width]-[tile height]-[dữ liệu map (số cột * số hàng)]
try{
InputStream is=getClass().getResourceAsStream("/"+fn);
mw=is.read();
mh=is.read();
tw=is.read();
th=is.read();
map=new byte[is.available()];
is.read(map);
//tạo TiledLayer tl từ những gì đã nhận được.
tl=new TiledLayer(mw,mh,tiles,tw,th);
//lát gạch vào TiledLayer tl.
for(int i=0;i<mh;i++){
for(int j=0;j<mw;j++){
tl.setCell(j, i, map[i*mw+j]);
}}
this.w=w;
this.h=h;
x=w/2-c*tw;
y=h/2-r*th;
//tạo file ảnh cho nhân vật phụ và khởi tạo ot ở vị trí cột 4 và hàng 6 trên map.
other=Image.createImage("/other.png");
ot=new other[3];
for(int i=0;i<ot.length;i++)
{ot[i]=new other(other,(i+1)*12*16,(i+1)*13*16);
Thread.sleep(50);}
}catch(Exception e){}} 
//hàm paint dùng vẽ tl lên Graphics g, và nhận giá trị phím bấm vào hàm move.
void paint(Graphics g,int k){
    move(k);
tl.paint(g);
//vẽ nhân vật phụ lên map.
for(int i=0;i<ot.length;i++)
ot[i].paint(g,this);}
//hàm move nhận giá trị phím bấm k và kiểm tra điều kiện để thay đổi tọa độ của map.
void move(int k){
    //do mỗi lần di chuyển ta cho chuyển 16 pixel, nên ta cần chia ra 4 lần di chuyễn nhỏ để hình ảnh mượt hơn.
    //nếu thấy không cần bước di chuyển nhỏ nào nữa thì cho di gán di chuyển tiếp
if(step==0){
    //gán tọa độ cũ vào các biến gx gy.
    int gx=x,gy=y;
    //tính tọa độ mới.
    switch(k){
        case -1:
            gy+=16;
    break;
    case -2:
            gy-=16;
    break;
    case -3:
            gx+=16;
    break;
    case -4:
            gx-=16;
    break;}
    //kiểm tra xem toa6 độ mới có chạm tường không.
if(check(w/2-gx,h/2-gy)){
this.k=k;
step=4;}
}else{
    //nếu còn lượt di chuyển nhỏ step!=0, thì xét đến thời gian di chuyển lần cuối.
    if(lm<=curr()-30){
    step--;
    switch(this.k){
        case -1:
            y+=th/4;
    break;
        case -2:
            y-=th/4;
    break;
        case -3:
            x+=tw/4;
    break;
        case -4:
            x-=tw/4;
    break;}
    lm=curr();}
}
//gán vị trí cho tl.
tl.setPosition(x, y);
}
  long curr(){return System.currentTimeMillis();}
  //hàm kiểm tra xem có chạm tường không, tường là gạch số 3.
  boolean check(int a,int b){
 return tl.getCell(a/16, b/16)<3;}
}

File other.java : other.java
C:\Users\thanhliem\Documents\NetBeansProjects\RPG\src\other.java
import javax.microedition.lcdui.*;
import javax.microedition.lcdui.game.*;
import java.util.*;

public class other {
    //bao gồm tọa độ của other (x,y), khung hình hiện tại f, phía di chuyển k, step
  int x,y,f=0,k=1;
  //dùng Sprite để tạo hình ảnh nhân vật phụ
  Sprite other;
  //mốc thời gian lần di chuyển cuối và lần đổi hướng cuối.
  long lm,lc;
  //hình ảnh dùng tạo Sprite
  Image im;
  //biến ngẫu nhiên dùng để tạo hướng ngẫu nhiên.
  Random rd=new Random();
  //hàm khởi tạo nhân các tham số hình ảnh và tọa độ ban đầu của other.
  public other(Image im,int x,int y){
  this.x=x;
  this.y=y;
  this.im=im;
  other=new Sprite(im,16,16);
  other.setFrame(f);
  }
  //hàm paint dùng vẽ nhân vật other lên Graphics g.
  void paint(Graphics g,map m){
  move(m);
  other.paint(g);
  }
  //hàm move nhân giá trị là biến map m, sử dụng để điều hướng nhân vật phụ.
  void move(map m){
      //so sánh mốc chuyển hướng cuối cùng để cho cứ 1.5s thì chuyển hướng.
  if(lc<m.curr()-1500){
      //với phép chia dư này, k chỉ nhận giá trị từ 1-4.
  k=Math.abs(rd.nextInt()%4)+1;
  lc=m.curr();}
  //so sánh với lần di chuyển cuối cùng, nếu cách 0.5s thì cho di chuyển.
  if(lm<m.curr()-200){
      f=f%2==0?(k-1)*2+1:(k-1)*2;
      other.setFrame(f);
      //gán tọa độ cũ vào các biến gx, gy.
      int gx=x,gy=y;
      //tính tọa độ mới.
      switch(k){
          case 1:
              y-=4;
      break;
          case 2:
              y+=4;
      break;
          case 3:
              x-=4;
      break;
          case 4:
              x+=4;
      break;}
      lm=m.curr();
      //tọa độ của nhân vật phụ được hình thành từ tọa độ của bản đồ và tọa độ của nhân vật phụ.
  
     //kiểm tra xem tọa độ mới có chạm tường không, nếu có thì cho quay lại tọa độ cũ.
 if(!m.check(x, y))
  {x=gx; y=gy;}
  }
  other.setPosition(x+m.x,y+m.y);
  }
}

Có vài sai sót nhỏ đã được chỉnh lại cho đúng, tại lúc khởi tạo nhân vật phụ trong map.java không phải thêm w/2- và h/2- mà xác định trực tiếp qua cột và hàng mà nhân vật phụ xuất hiện. source jar

một nhân vật phụ họa trong trò chơi

Một nhân vật chạy tới chạy lui, cũng khá là hay và vui mắt, điều này còn làm cho trò chơi trở nên sinh động hơn. Chúng ta sẽ thêm một lớp other.java như sau. other.java
C:\Users\thanhliem\Documents\NetBeansProjects\RPG\src\other.java
import javax.microedition.lcdui.*;
import javax.microedition.lcdui.game.*;
import java.util.*;

public class other {
    //bao gồm tọa độ của other (x,y), khung hình hiện tại f, phía di chuyển k, step
  int x,y,f=0,k=1;
  //dùng Sprite để tạo hình ảnh nhân vật phụ
  Sprite other;
  //mốc thời gian lần di chuyển cuối và lần đổi hướng cuối.
  long lm,lc;
  //hình ảnh dùng tạo Sprite
  Image im;
  //biến ngẫu nhiên dùng để tạo hướng ngẫu nhiên.
  Random rd=new Random();
  //hàm khởi tạo nhân các tham số hình ảnh và tọa độ ban đầu của other.
  public other(Image im,int x,int y){
  this.x=x;
  this.y=y;
  this.im=im;
  other=new Sprite(im,16,16);
  other.setFrame(f);
  }
  //hàm paint dùng vẽ nhân vật other lên Graphics g.
  void paint(Graphics g,map m){
  move(m);
  other.paint(g);
  }
  //hàm move nhân giá trị là biến map m, sử dụng để điều hướng nhân vật phụ.
  void move(map m){
      //so sánh mốc chuyển hướng cuối cùng để cho cứ 1.5s thì chuyển hướng.
  if(lc<m.curr()-1500){
      //với phép chia dư này, k chỉ nhận giá trị từ 1-4.
  k=Math.abs(rd.nextInt()%4)+1;
  lc=m.curr();}
  //so sánh với lần di chuyển cuối cùng, nếu cách 0.5s thì cho di chuyển.
  if(lm<m.curr()-200){
      f=f%2==0?(k-1)*2+1:(k-1)*2;
      other.setFrame(f);
      switch(k){
          case 1:
              y-=4;
      break;
          case 2:
              y+=4;
      break;
          case 3:
              x-=4;
      break;
          case 4:
              x+=4;
      break;}
      lm=m.curr();
  }
  //tọa độ của nhân vật phụ được hình thành từ tọa độ của bản đồ và tọa độ của nhân vật phụ.
  other.setPosition(x+m.x,y+m.y);
  }
}

Và ta sẽ sử dụng hình ảnh này để tạo nhân vật phụ :

Ta sẽ thêm vào trong map.java để sử dụng nhân vật phụ này : map.java
C:\Users\thanhliem\Documents\NetBeansProjects\RPG\src\map.java
import java.io.*;
import javax.microedition.lcdui.game.*;
import javax.microedition.lcdui.*;

public class map {
    //các thông số gồm có số cột, số hàng trong map, kích thước mỗi tile tw, th.
    //tọa độ map (x,y), phím bấm k, số lần di chuyển nhỏ.
    int mw,mh,tw,th,x,y,k,step=0;
    //chứa dữ liệu các tile trong map.
    byte[] map;
    //hình ảnh dùng làm tile
    Image tiles,other;
    //dùng class TiledLayer có sẵn trong GameCanvas để tạo map.
    TiledLayer tl;
    //mốc thời gian di chuyển lần cuối
    long lm=0;
    //thêm một biến other ot dùng làm nhân vật phụ.
        other ot;
    //hàm khởi tạo sẽ tải hình ảnh làm tile vào class map.
    public map(String im){
    try{
    this.tiles=Image.createImage("/"+im);
    }catch(Exception e){}}
    //hàm load dùng tải file nguồn và phân tích các thông số trong đó vào map.
    //bây giờ ta có thông số xác định tọa độ ban đầu nhân vật, tại cột c, dòng r và so sánh với độ dài rộng màn hình.
public void load(String fn,int c,int r,int w,int h){
    //file nguồn là dữ liệu với cấu trúc: [số cột]-[số hàng]-[tile width]-[tile height]-[dữ liệu map (số cột * số hàng)]
try{
InputStream is=getClass().getResourceAsStream("/"+fn);
mw=is.read();
mh=is.read();
tw=is.read();
th=is.read();
map=new byte[is.available()];
is.read(map);
//tạo TiledLayer tl từ những gì đã nhận được.
tl=new TiledLayer(mw,mh,tiles,tw,th);
//lát gạch vào TiledLayer tl.
for(int i=0;i<mh;i++){
for(int j=0;j<mw;j++){
tl.setCell(j, i, map[i*mw+j]);
}}
x=w/2-c*tw;
y=h/2-r*th;
//tạo file ảnh cho nhân vật phụ và khởi tạo ot ở vị trí cột 4 và hàng 6 trên map.
other=Image.createImage("/other.png");
    ot=new other(other,w/2-4*16,h/2-6*16);
}catch(Exception e){}} 
//hàm paint dùng vẽ tl lên Graphics g, và nhận giá trị phím bấm vào hàm move.
void paint(Graphics g,int k){
    move(k);
tl.paint(g);
//vẽ nhân vật phụ lên map.
ot.paint(g,this);}
//hàm move nhận giá trị phím bấm k và kiểm tra điều kiện để thay đổi tọa độ của map.
void move(int k){
    //do mỗi lần di chuyển ta cho chuyển 16 pixel, nên ta cần chia ra 4 lần di chuyễn nhỏ để hình ảnh mượt hơn.
    //nếu thấy không cần bước di chuyển nhỏ nào nữa thì cho di gán di chuyển tiếp
if(step==0){
this.k=k;
step=4;
}else{
    //nếu còn lượt di chuyển nhỏ step!=0, thì xét đến thời gian di chuyển lần cuối.
    if(lm<=curr()-30){
    step--;
    switch(this.k){
        case -1:
            y+=th/4;
    break;
        case -2:
            y-=th/4;
    break;
        case -3:
            x+=tw/4;
    break;
        case -4:
            x-=tw/4;
    break;}
    lm=curr();}
}
//gán vị trí cho tl.
tl.setPosition(x, y);
}
  long curr(){return System.currentTimeMillis();}
}

Rồi vậy là xong, kết quả đây :

source jar

di chuyển bản đồ mà tưởng nhân vật đang di chuyển vậy

Thay vì di chuyển nhân vật, nhân vật ta luôn làm trung tâm và chúng ta chỉ cho bản đồ di chuyển. mọi thứ sẽ thay đổi nhiều so với lần trước, nhưng cũng không đến nổi khó hiểu cho lắm.
Mỗi lần bản đồ di chuyển một khoảng là một ô gạch, như vậy thì ta sẽ thấy nó rất giật, cho nên mỗi lần bấm phím di chuyển ta cho bản đồ di chuyển thành nhiều bước nhỏ, mỗi bước nhỏ là một ước số của kích thước ô gạch. Làm như vậy, nhân vật và bản đồ luôn khớp với nhau tại từng ô, chứ nhân vật không bao giờ nằm lưng chừng giữa 2 ô gạch, điều này sẽ giúp ta xác định điểm chạm ở bài sau.
File map.java trở thành thế này : map.java
C:\Users\thanhliem\Documents\NetBeansProjects\RPG\src\map.java
import java.io.*;
import javax.microedition.lcdui.game.*;
import javax.microedition.lcdui.*;

public class map {
    //các thông số gồm có số cột, số hàng trong map, kích thước mỗi tile tw, th.
    //tọa độ map (x,y), phím bấm k, số lần di chuyển nhỏ.
    int mw,mh,tw,th,x,y,k,step=0;
    //chứa dữ liệu các tile trong map.
    byte[] map;
    //hình ảnh dùng làm tile
    Image tiles;
    //dùng class TiledLayer có sẵn trong GameCanvas để tạo map.
    TiledLayer tl;
    //mốc thời gian di chuyển lần cuối
    long lm=0;
    //hàm khởi tạo sẽ tải hình ảnh làm tile vào class map.
    public map(String im){
    try{
    this.tiles=Image.createImage("/"+im);}catch(Exception e){}}
    //hàm load dùng tải file nguồn và phân tích các thông số trong đó vào map.
    //bây giờ ta có thông số xác định tọa độ ban đầu nhân vật, tại cột c, dòng r và so sánh với độ dài rộng màn hình.
public void load(String fn,int c,int r,int w,int h){
    //file nguồn là dữ liệu với cấu trúc: [số cột]-[số hàng]-[tile width]-[tile height]-[dữ liệu map (số cột * số hàng)]
try{
InputStream is=getClass().getResourceAsStream("/"+fn);
mw=is.read();
mh=is.read();
tw=is.read();
th=is.read();
map=new byte[is.available()];
is.read(map);
//tạo TiledLayer tl từ những gì đã nhận được.
tl=new TiledLayer(mw,mh,tiles,tw,th);
//lát gạch vào TiledLayer tl.
for(int i=0;i<mh;i++){
for(int j=0;j<mw;j++){
tl.setCell(j, i, map[i*mw+j]);
}}
x=w/2-c*tw;
y=h/2-r*th;
System.out.println("finish");
}catch(Exception e){}} 
//hàm paint dùng vẽ tl lên Graphics g, và nhận giá trị phím bấm vào hàm move.
void paint(Graphics g,int k){
    move(k);
tl.paint(g);}
//hàm move nhận giá trị phím bấm k và kiểm tra điều kiện để thay đổi tọa độ của map.
void move(int k){
    //do mỗi lần di chuyển ta cho chuyển 16 pixel, nên ta cần chia ra 4 lần di chuyễn nhỏ để hình ảnh mượt hơn.
    //nếu thấy không cần bước di chuyển nhỏ nào nữa thì cho di gán di chuyển tiếp
if(step==0){
this.k=k;
step=4;
}else{
    //nếu còn lượt di chuyển nhỏ step!=0, thì xét đến thời gian di chuyển lần cuối.
    if(lm<=curr()-30){
    step--;
    switch(this.k){
        case -1:
            y+=th/4;
    break;
        case -2:
            y-=th/4;
    break;
        case -3:
            x+=tw/4;
    break;
        case -4:
            x-=tw/4;
    break;}
    lm=curr();}
}
//gán vị trí cho tl.
tl.setPosition(x, y);
}
  long curr(){return System.currentTimeMillis();}
}

Và vì vậy trong RPG.java ta cũng thay đổi cách sử dụng các hàm trong map.java, nhân vật chúng ta sẽ di chuyển, nhưng thật chất là thay đổi vị trí bản đồ trên màn hình. RPG.java
C:\Users\thanhliem\Documents\NetBeansProjects\RPG\src\RPG.java
import javax.microedition.lcdui.*;
import javax.microedition.lcdui.game.*;
public class RPG extends Canvas{
    //các biến này bao gồm kích thước màn hình w,h, khung hiện tại hiện thị f, phím nhấn k
    int w,h,f=2,k;
    //biến thời gian ghi lại mốc thời gian di chuyển cuối cùng
    long lm=0;
    //nhân vật được tạo qua lớp Sprite là có sẵn trong GameCanvas
    Sprite nv;
    //hình ảnh sử dụng
    Image im;
    //tạo biến load map m, với hình ảnh là tiles.png .
    map m=new map("tiles.png");
  public RPG(){
  setFullScreenMode(true);
  w=getWidth();
  h=getHeight();
  try{
      //tạo hình ảnh trước
  im=Image.createImage("/nv.png");
  //tạo Sprite sau khi đã có hình ảnh, mỗi khung nhỏ là 16x16
  nv=new Sprite(im,16,16);}catch(Exception e){}
  //load map m0.mbd vào biến map m, nhân vật ở vị trí cột 3, dòng 4 trên map.
  m.load("m0.mbd",3,4,w,h);}
  public void paint(Graphics g){
      //lệnh tô màn hình
  g.setColor(0x6060a0);
  g.fillRect(0,0,w,h);
  //hàm xử lí di chuyển theo phím bấm
  move();
  //vẽ map ở vị trí (0,0).
  m.paint(g,k);
  //vẽ nhân vật ra màn hình thông qua hàm paint có sẵn của lớp Sprite
  nv.paint(g);
  //cho vẽ lại
  repaint();}
  public void keyPressed(int k){this.k=k;}
  public void keyReleased(int k){this.k=0;}
  //hàm di chuyển sẽ xữ lí phím bấm k để xác định khung của nhân vật
  void move(){
      //ở đây cho kiểm tra nếu lần di chuyển cuối cùng cách thời gian hiện tại 20 milli giây thì cho di chuyển tiếp.
  if(lm<curr()-20&&k!=0){
      //công thức xử lí phím và khung hiện tại để cho ra khung ảnh phù hợp, bạn tự thế vào và xem nó có đúng không nhé.
      f=f%2==0?(-k-1)*2+1:(-k-1)*2;
      //sau khi di chuyển thì cho mốc thời gian lần cuối thay đổi.
      lm=curr();
  }
  //đặt giá trị khung ảnh và vị trí nhân vật trên màn hình.
  nv.setFrame(f);
  nv.setPosition(w/2,h/2);
  }
  //hàm trả về thời gian hiện tại, do sử dụng nhiều lần nên làm riêng một hàm cho khõi phải mất công viết lại.
  long curr(){return System.currentTimeMillis();}
}

Kết quả là đây : source jar

Load map từ file nguồn

do bản đồ trong game RPG là rất rộng lớn cho nên ta sẽ không đặt map thông qua mảng trực tiếp mà sẽ tải dữ liệu từ file map, đó là file được tạo ra từ phần mềm http://www.mapeditor.org/ và được xử lí qua phần chuyển đổi http://holyeyed.99k.org/function/tombd.php để dễ sử dụng hơn. Và đây là file dùng để load map.mbd và sử dụng trong trò chơi. trước khi lưu file map trong phần mềm tiled map editor, bạn vào edit -> reference và thiết lặp như sau (store tile layer data as CSV):
map.java
C:\Users\thanhliem\Documents\NetBeansProjects\RPG\src\map.java
import java.io.*;
import javax.microedition.lcdui.game.*;
import javax.microedition.lcdui.*;

public class map {
    //các thông số gồm có số cột, số hàng trong map, kích thước mỗi tile tw, th.
    int mw,mh,tw,th;
    //chứa dữ liệu các tile trong map.
    byte[] map;
    //hình ảnh dùng làm tile
    Image tiles;
    //dùng class TiledLayer có sẵn trong GameCanvas để tạo map.
    TiledLayer tl;
    //hàm khởi tạo sẽ tải hình ảnh làm tile vào class map.
    public map(String im){
    try{
    this.tiles=Image.createImage("/"+im);}catch(Exception e){}}
    //hàm load dùng tải file nguồn và phân tích các thông số trong đó vào map.
public void load(String fn){
    //file nguồn là dữ liệu với cấu trúc: [số cột]-[số hàng]-[tile width]-[tile height]-[dữ liệu map (số cột * số hàng)]
try{
InputStream is=getClass().getResourceAsStream("/"+fn);
mw=is.read();
mh=is.read();
tw=is.read();
th=is.read();
map=new byte[is.available()];
is.read(map);
//tạo TiledLayer tl từ những gì đã nhận được.
tl=new TiledLayer(mw,mh,tiles,tw,th);
//lát gạch vào TiledLayer tl.
for(int i=0;i<mh;i++){
for(int j=0;j<mw;j++){
tl.setCell(j, i, map[i*mw+j]);
}}
}catch(Exception e){}} 
//hàm paint dùng vẽ tl lên Graphics g, tại vị trí (x,y).
void paint(Graphics g,int x,int y){
    tl.setPosition(x, y);
tl.paint(g);}
}

ở đây ta dùng file tiles.png này làm gạch lát:

Chúng ta sẽ sử dụng nó trong RPG.java như sau. RPG.java
C:\Users\thanhliem\Documents\NetBeansProjects\RPG\src\RPG.java
import javax.microedition.lcdui.*;
import javax.microedition.lcdui.game.*;
public class RPG extends Canvas{
    //các biến này bao gồm kích thước màn hình w,h, khung hiện tại hiện thị f, phím nhấn k
    int w,h,f=2,k;
    //biến thời gian ghi lại mốc thời gian di chuyển cuối cùng
    long lm=0;
    //nhân vật được tạo qua lớp Sprite là có sẵn trong GameCanvas
    Sprite nv;
    //hình ảnh sử dụng
    Image im;
    //tạo biến load map m, với hình ảnh là tiles.png .
    map m=new map("tiles.png");
  public RPG(){
  setFullScreenMode(true);
  w=getWidth();
  h=getHeight();
  try{
      //tạo hình ảnh trước
  im=Image.createImage("/nv.png");
  //tạo Sprite sau khi đã có hình ảnh, mỗi khung nhỏ là 16x16
  nv=new Sprite(im,16,16);}catch(Exception e){}
  //load map m0.mbd vào biến map m.
  m.load("m0.mbd");}
  public void paint(Graphics g){
      //lệnh tô màn hình
  g.setColor(0x6060a0);
  g.fillRect(0,0,w,h);
  //hàm xử lí di chuyển theo phím bấm
  move();
  //vẽ map ở vị trí (0,0).
  m.paint(g,0,0);
  //vẽ nhân vật ra màn hình thông qua hàm paint có sẵn của lớp Sprite
  nv.paint(g);
  //cho vẽ lại
  repaint();}
  public void keyPressed(int k){this.k=k;}
  public void keyReleased(int k){this.k=0;}
  //hàm di chuyển sẽ xữ lí phím bấm k để xác định khung của nhân vật
  void move(){
      //ở đây cho kiểm tra nếu lần di chuyển cuối cùng cách thời gian hiện tại 20 milli giây thì cho di chuyển tiếp.
  if(lm<curr()-20&&k!=0){
      //công thức xử lí phím và khung hiện tại để cho ra khung ảnh phù hợp, bạn tự thế vào và xem nó có đúng không nhé.
      f=f%2==0?(-k-1)*2+1:(-k-1)*2;
      //sau khi di chuyển thì cho mốc thời gian lần cuối thay đổi.
      lm=curr();
  }
  //đặt giá trị khung ảnh và vị trí nhân vật trên màn hình.
  nv.setFrame(f);
  nv.setPosition(w/2,h/2);
  }
  //hàm trả về thời gian hiện tại, do sử dụng nhiều lần nên làm riêng một hàm cho khõi phải mất công viết lại.
  long curr(){return System.currentTimeMillis();}
}

Và kết quả như sau :

source jar