使用图层创建地图

作者:潇潇  出处:www.j2mefans.com
该文章为本站原创,如有引用,请注明出处和作者

    
什么是层呢?形象的说就和我们穿的衣服一样,里面一件构成一层,外面一件也是一层。当然这仅是就语义上的解释,并不能准确地说明什么是层。概括地说就是体
现画面元素远近的层次。手机游戏也要使用到这一概念。虽然我们这里所说的游戏还是2D的,但我们也要使用层的方法来表现物体和屏幕的远近关系。
    
可以这样想象分层的原理:动画内容中表现的虚拟空间,我们可以根据画面中元素的远近,将空间分为若干个层,靠后的层离屏幕远,先绘制,在绘制时会被离屏幕
近的层所覆盖,靠前的层离屏幕近,后绘制,在绘制时会覆盖先绘制的层。这样就可以使用层的概念,将2维的平面变成了三维的空间,其中包含局屏幕远近不同的
平面,每一平面就是一个层。将各层按照一定的前后顺序排列好后,最后显示的图像就是沿Z轴的负方向看到的画面。
    我们在前面看到的精灵,它可以在背景上移动,其实就是一个很好的例子。其中精灵是一层,背景是一层,精灵靠前,背景在后面,精灵可以阻挡部分背景画面。
    以上所说的也就是sprite类的基类layer类。
    
和sprite一样,layer类还延伸出了一个TiledLayer类。这个类有什么用呢?我们知道手机的内存是很小的,可是我们玩的游戏又常常会存在
一个很大的地图,当然我们不肯能也不愿意将一个完整的地图图片完完全全的显示到屏幕上,毕竟这样也会在内存中有一个完整的地图图片存在。即使内存再大,都
是一种对资源的浪费。有什么好方法呢?
    我们可以仅仅显示比屏幕稍大些的地图的话,那么我们就可以有效的减少内存的使用。怎么做呢?那就是
快速的计算屏幕显示的范围大小,然后仅仅显示这一部分。又因为在一般的地图中往往会有许多重复的地方,比如大块的样式相同的马路,所以,我们完全可以将一
张背景图片切分为几个不同的图快,切割后的相同图片仅保存一份。使用这样的方法,我们就可以使用简单的几块图块,拼出一幅幅不同的大图片来了。赫赫,是否
有点像拼图游戏呢?如果您能认识到这一点,那么你入门了。
    下面我们看一看在MIDP2。0种怎样使用该方法。
    MIDP2.0中我们通过TiledLayer类支持上面谈到的平铺图层方法。该类使得创建和使用图层变得相对容易。具体步骤:

    1、创建平铺图层。
     以贴图为单位指定每行及每列贴图的数量,以及包含贴图的图像,每一个贴图的宽度和高度,下面是典型的代码:
TiledLayer backgroundLayer;
try(
//构造方法要求五个参数,列数,行数,图像 ,图像元素的宽度和高度。
    backgroundLayer = new TiledLayer( 12,4,Image.createImage(),32,34);
} catch (IOException e) {}
也就是说,我们只要设定要切片的图像,并且告诉切片的大小,以及每行每列中切片的数目,TiledLayer类自动会给我们完成切图的效果。就像在精灵类中告诉他图片和帧的大小就能切出每一帧一样。他的切图方向是从左到右,从上到下,像写字一样的顺序。如下图所示:
 

2、设定每个单元的显示切图
    
在使用TiledLayer类后,他会根据读入的图片建立一个静态的图片阵列,其索引值从1开始。然后根据建构的需求,建立名称为Cells的2维阵列,
默认情况下,Cells每个元素的内容都是0。当图层绘制时,会根据每个Cell内部的索引值来绘制画面。如果遇到Cell的索引值为0时,代表这个区域
不该有任何图形,在屏幕上显示为透明的区域。
    所以我们要使用setCell函数,根据地图,将Cells阵列,填充相应的索引值。下面是示例:
int[] map =
{
0,0,1,3,0,0,0,0,0,0,0,0,
0,1,4,4,3,0,0,0,0,1,2,2,
1,4,4,4,4,3,0,0,1,4,4,4,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
};

for( int i=0;i<map.length;i++)
{
    int column = i%12;
    int row = (i-column)/12;
    tiledLayer.setCell(column,row,map[i]);
    System.out.println(i + “;” + column + “;” + row);
}
如下图所示:
 
3、设定地图显示的左上点坐标。
    对于这一点我们可以使用setPosition函数,该函数和sprite类中的同名函数都出自Layer类。
4、显示地图。
    和设定显示坐上点坐标的方法一样,显示地图的方法也继承自Layer类的paint方法。
5、移动地图
    为什么要移动地图呢?我们知道除了固定地图大小的游戏外,我们常见的还有纵向和横向卷轴游戏,现在常见的还有向四个方向移动的游戏,他们的共同点都是人物处在屏幕的正中间,当人物移动时,地图向相反的方向移动。
    那么我们怎样实现这个效果呢?很简单的,可能大家都想到了。没错就是TiledLayer类的父类Layer类中的move函数。毕竟TiledLayer类和Sprite类都继承自Layer类啊。

    下面我们给出一个完整的例子。
    我们先来看一下我们的地图元素(为了看得方便,我已经将图片用红线进行了切割,所以很明显的可以看出每块图片元素的样子):
 
    我们还是先用它来拼一个地图吧。下面是画布类的源代码:
import javax.microedition.lcdui.*;
import javax.microedition.lcdui.game.*;
import java.util.*;
import java.io.*;

public class OCanvas extends GameCanvas implements Runnable {
  private Display    display;
  private boolean    sleeping;
  private long       frameDelay;
  private int        inputDelay;
  private TiledLayer backgroundLayer;
  
  public OCanvas(Display d) {
    super(true);
    display = d;

    // 设定帧频 (30 fps)
    frameDelay = 33;

    //清除输入延迟
    inputDelay = 0;
  }
  
  public void start() {
    // 设定这个画布为当前屏幕显示
    display.setCurrent(this);

    // 创建背景图层
    try {
      backgroundLayer = new TiledLayer(16, 16, Image.createImage("/Background.png"), 48, 48);
    }
    catch (IOException e) {
      System.err.println("Failed loading images!");
    }

    // 设定背景图层地图
    int[] layerMap = {
      3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,
      3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,
      3, 21, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 22,  3,
      3, 18,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2, 20,  3,
      3, 18,  2,  2,  2,  5, 15, 15, 15, 15, 15, 15,  6,  2, 20,  3,
      3, 18,  2,  2,  2,  7, 10,  1,  1,  1,  1,  1, 16,  2, 20,  3,
      3, 18,  2,  2,  2,  2, 14,  1,  1,  1,  1,  1, 16,  2, 20,  3,
      3, 18,  2,  2,  2,  2,  7, 10,  1,  1,  1,  1, 16,  2, 20,  3,
      3, 18,  2,  2,  2,  2,  2, 14,  1,  1,  1,  1, 16,  2, 20,  3,
      3, 18,  2,  2,  2,  2,  2, 14,  1,  9, 10,  1, 16,  2, 20,  3,
      3, 18,  2,  5, 15,  6,  2, 14,  1, 11, 12,  1, 16,  2, 20,  3,
      3, 18,  2, 14,  1, 16,  2,  7, 13, 13, 13, 13,  8,  2, 20,  3,
      3, 18,  2,  7, 13,  8,  2,  2,  2,  2,  2,  2,  2,  2, 20,  3,
      3, 18,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2, 20,  3,
      3, 23, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 24,  3,
      3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3
    };
    for (int i = 0; i < layerMap.length; i++) {
      int column = i % 16;
      int row = (i – column) / 16;
      backgroundLayer.setCell(column, row, layerMap[i]);
    }
    backgroundLayer.setPosition((getWidth() – backgroundLayer.getWidth()) / 2,
      (getHeight() – backgroundLayer.getHeight()) / 2);

    // 开始动画线程
    sleeping = false;
    Thread t = new Thread(this);
    t.start();
  }
  
  public void stop() {
    // 停止动画
    sleeping = true;
  }
  
  public void run() {
    Graphics g = getGraphics();
    
    // 游戏主循环
    while (!sleeping) {
      //更新数据
    update();
     //显示图像
      draw(g);
      try {
        Thread.sleep(frameDelay);
      }
      catch (InterruptedException ie) {}
    }
  }

  private void update() {
    // 根据用户的输入,去移动背景图层
    if (++inputDelay > 2) {
      int keyState = getKeyStates();
      if ((keyState & LEFT_PRESSED) != 0) {
        backgroundLayer.move(12, 0);
      }
      else if ((keyState & RIGHT_PRESSED) != 0) {
        backgroundLayer.move(-12, 0);
      }
      if ((keyState & UP_PRESSED) != 0) {
        backgroundLayer.move(0, 12);
      }
      else if ((keyState & DOWN_PRESSED) != 0) {
        backgroundLayer.move(0, -12);
      }
      
      // 重置输入延迟
      inputDelay = 0;
    }
  }

  private void draw(Graphics g) {
    // 绘制背景图层
    backgroundLayer.paint(g);
       
    //刷新屏幕显示缓冲区
    flushGraphics();
  }
  
}