線程在J2ME開發中是不可或缺的一部分,J2ME繼承了J2SE中關於java.lang中的Runnable接口,以及Thread類。但是,由於J2ME應用的特殊性,J2ME程序中去除了部分API,沒有線程組的概念,也沒有daemon線程。
今天,我們從一個例子出發,來學習J2ME當中的線程的概念。我們選取的例子是俄羅斯方塊。首先,有一些要注意的事項:
1.注意一點,要注意在J2me中不要使用浮點數,這樣可以通過編譯,但是不能通過預驗證。因為一般手持設備都無法負擔浮點運算的高負荷。
2.在J2ME程序當中,絕大多數的空間為圖片所占有,我們可以看到,今天我們的例子沒有任何圖片,僅僅5k,如果是開發產品,不可避免的要使用圖片,
但是盡量使用壓縮率高的png圖片,而且不要太過復雜,因為復雜的圖片會使得圖片變得很大。
3.在程序中盡量使用常量特別是位置信息,這樣當作修改的時候只要改一個量就可以了,而且當移植到其他平台的時候也會減少很多工作量.還有就是顏色
信息等.不用每次記憶,重新構造,因為J2me中的顏色和j2se的不太一樣.沒有常量定義.
4.游戲產品經常需要保護版權,而當今的很多反編譯工具可以輕而易舉地把jar文件的內容反編譯過來,因此可以對程序進行模糊化處理,使得無法反編譯
或者反編譯後無法理解.可以右鍵點擊項目,在屬性中選擇Build|Obfuscating,選擇模糊化級別.
5.講解中我們都使用NetBeans作為開發平台,有關安裝事宜請訪問www.netbeans.org.
好,我們開始吧。
A. 首先,建立一個新的移動應用程序項目,取名Tetris, 不要自動創建Hello程序,選取MIDP1.0和CLDC1.0.
B. 新建一個包,方法是右鍵點擊項目,選取New|Java Package,取名Tetris.
C. 新建一個Midlet,同上,選取New|Java Midlet, 取名TetrisMidlet.
D. 我們需要一個能夠顯示游戲的Canvas, 因此新建一個Class名叫TetrisCanvas, 在TetrisMidlet.java中將TetrisCanvas作為當前可以顯示的元素:
現在的TetrisMidlet.java如下:
package Tetris;
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
/**
*
* @author lin
* @version
*/
public class TetrisMidlet extends MIDlet {
public void startApp() {
Display display = Display.getDisplay( this );
// TetrisCanvas extends Canvas which extends Displayable so it can
// be displayed directly
display.setCurrent( new TetrisCanvas());
}
public void pauseApp() {
}
public void destroyApp(boolean unconditional) {
}
}
由於TetrisCanvas繼承了Canvas,所以可以被TetrisMidlet所顯示.
E. 這裡,我們需要將TetrisCanvas繼承Canvas,並且實現Canvas的接口函數paint(),我們現在有了一個TetrisCanvas的框架了。
package Tetris;
import javax.microedition.lcdui.*;
public class TetrisCanvas extends Canvas {
/** Creates a new instance of TetrisCanvas */
public TetrisCanvas() {
}
protected void paint(Graphics g){
}
}
下面我們需要使得TetrisCanvas具有Thread的特性,這裡有兩種方法,一種是讓TetrisCanvas繼承Thread類,然後生成它的實例,但是由於它已經
繼承了Canvas類,而Java中不允許多重繼承,因此,我們在編程當中通常采取第二種做法,也就是讓它實現Runnable接口,在成員中聲明一個Thread
成員,實例生成指向自己,然後實現run方法。
也就是這樣:
public class TetrisCanvas extends Canvas implements Runnable {
private Thread Blocker = null;
...
public TetrisCanvas(){
Blocker = new Thread(this);
Blocker.start();
}
...
public void run(){
while (Blocker != null) {
}
}
...
}
F. 程序邏輯:下面給出程序清單。程序中我們使用一個數組來存儲方塊的信息,一共有十九種,還有一個數組來存儲當前的畫面方格的內容.在程序中
有一個paint方法來實現重畫,注意繪制的先後次序,當程序規模變得很大的時候,重畫的效率就非常重要,需要進行優化.我們在程序中使用了背景,
在沒有背景的情況下,程序僅5k,采用背景後,程序47k,可見對圖片的優化至關重要.
/*
* TetrisCanvas.java
*
* Created on 2005年7月13日, 上午11:31
*
* To change this template, choose Tools | Options and locate the template under
* the Source Creation and Management node. Right-click the template and choose
* Open. You can then make changes to the template in the Source Editor.
*/
package Tetris;
import java.util.*;
import java.lang.Math;
import javax.microedition.lcdui.*;
/**
*
* @author lin
*/
public class TetrisCanvas extends Canvas implements Runnable{
private Thread Blocker = null;
private Random generator;
private int FutureBlockType, BlockType,LastType,LastX,LastY,BlockX,BlockY ;
private int BlockLines,BlockScore;
private int BlockSpeed,CurSpeed;
private static final int COLOR_GRAY = 0x00eeeeee;
private static final int COLOR_RED = 0x00ff0000;
private static final int COLOR_BLACK = 0x00000000;
private static final int COLOR_WHITE = 0x00ffffff;
private static final int COLOR_BLUE = 0x000000ff;
private static final int COLOR_LIGHT_BLUE= 0x0089a5d1;
private static final int COLOR_DARK_GRAY = 0x00808080;
private static final int COLOR_BACKGROUND= COLOR_LIGHT_BLUE;
private static final int BLOCK_SIZE = 7;
private static final int CANVAS_SIZE_WIDTH = 12;
private static final int CANVAS_SIZE_HEIGHT = 22;
private static final int CANVAS_OFFSET_X = 5;
private static final int CANVAS_OFFSET_Y = 7;
/**
* The paint status.
*/
boolean ISCLEAR = false;
boolean ISDOWN = false;
boolean ISDEL = false;
/**
* the block information matrix.
*/
int BlockInfo[][]={{1,0,1,1,1,2,1,3,0xff0000,2},
{0,1,1,1,2,1,3,1,0xff0000,4},
{0,0,0,1,1,1,1,2,0x0000ff,2},
{0,1,1,0,1,1,2,0,0x0000ff,3},
{0,1,0,2,1,0,1,1,0x00ff00,2},
{0,0,1,0,1,1,2,1,0x00ff00,3},
{0,0,0,1,1,0,1,1,0xffff00,2},
{0,1,1,0,1,1,1,2,0x00ffff,2},
{0,1,1,0,1,1,2,1,0x00ffff,3},
{1,0,1,1,1,2,2,1,0x00ffff,3},
{0,1,1,1,1,2,2,1,0x00ffff,3},
{0,1,0,2,1,1,2,1,0xff00ff,3},
{0,0,1,0,1,1,1,2,0xff00ff,3},
{0,1,1,1,2,0,2,1,0xff00ff,3},
{1,0,1,1,1,2,2,2,0xff00ff,3},
{0,0,0,1,1,1,2,1,0xffffff,3},
{1,0,1,1,1,2,2,0,0xffffff,3},
{0,1,1,1,2,1,2,2,0xffffff,3},
{0,2,1,0,1,1,1,2,0xffffff,3},
};
// Gridmatrix 中只存儲顏色信息
int Gridmatrix[][]=new int[CANVAS_SIZE_HEIGHT][CANVAS_SIZE_WIDTH];
/**
* Initialize the applet. Resize and load images.
*/
public void init() {
BlockType=Math.abs(generator.nextInt()%19);
FutureBlockType=Math.abs(generator.nextInt()%19);
LastType=BlockType;
BlockLines=0;
BlockScore=0;
BlockSpeed=1;
CurSpeed=BlockSpeed;
BlockX=4; LastX=BlockX;
BlockY=0; LastY=BlockY;
//初始化Gridmatrix矩陣,內容為帶邊框的主繪圖區。
for(int i=0;i
for(int j=0;j
Gridmatrix[i][j]=0;
for(int i=0;i
Gridmatrix[CANVAS_SIZE_HEIGHT-1][i]=COLOR_DARK_GRAY;
for(int i=0;i
Gridmatrix[i][0]=COLOR_DARK_GRAY;
Gridmatrix[i][11]=COLOR_DARK_GRAY;
}
}
/** Creates a new instance of TetrisCanvas */
public TetrisCanvas() {
generator = new Random( System.currentTimeMillis() );
init();
Blocker = new Thread(this);
Blocker.start();
}
private void draw3DBlock(Graphics g, int c, int x, int y, int width, int height){
int color = g.getColor();
g.setColor( COLOR_WHITE );
g.drawRect( x, y, width, height );
g.setColor(c);
g.fillRect( x + 1, y + 1, width-2, height-2 );
g.setColor( COLOR_BLACK );
g.drawLine( x + width-1, y, x + width-1, y + height-1 );
g.drawLine( x, y + height-1, x + width-1, y + height-1 );
g.setColor(color);
}
public static boolean drawText(Graphics g, String str, int x, int y, int anchor, int color, int size) {
Font f_old,f_new;
int c_old;
try {
f_old = g.getFont();
f_new = Font.getFont(Font.FACE_SYSTEM,Font.STYLE_BOLD,size);
g.setFont(f_new);
c_old = g.getColor();
g.setColor(color);
g.drawString(str, x, y, anchor );
g.setColor(c_old);
g.setFont(f_old);
return true;
}catch (Exception ex) {
return false;
}
}
protected void paint(Graphics g){
//畫背景
try{
Image image_Splash = Image.createImage("/back.png");
g.drawImage(image_Splash, 0, 0,Graphics.TOP | Graphics.LEFT);
}
catch(Exception ex) {
}
//畫下一個要出現的方塊
drawText(g, "下一個", 91, 5, Graphics.TOP| Graphics.LEFT, COLOR_BLUE, Font.SIZE_SMALL);
g.setColor(COLOR_GRAY);
g.drawRoundRect(91, 18, 26, 30, 2, 2);
g.setColor(COLOR_DARK_GRAY);
g.fillRoundRect(92, 19, 24, 28, 2, 2);
for(int i=0;i<=3;i++)
draw3DBlock(g, BlockInfo[FutureBlockType][8],
93+BlockInfo[FutureBlockType][i*2+1]*BLOCK_SIZE,
20+BlockInfo[FutureBlockType][i*2]*BLOCK_SIZE,
BLOCK_SIZE,BLOCK_SIZE);
drawText(g, "速度:"+String.valueOf(CurSpeed), 91, 60, Graphics.TOP| Graphics.LEFT, COLOR_BLUE, Font.SIZE_SMALL);
drawText(g, "行數:"+String.valueOf(BlockLines), 91, 75, Graphics.TOP| Graphics.LEFT, COLOR_BLUE, Font.SIZE_SMALL);
drawText(g, "成績:", 91, 90, Graphics.TOP| Graphics.LEFT, COLOR_BLUE, Font.SIZE_SMALL);
g.setColor(COLOR_GRAY);
g.drawRoundRect(91, 105, 26, 20, 2, 2);
g.setColor(COLOR_DARK_GRAY);
g.fillRoundRect(92, 106, 24, 18, 2, 2);
drawText(g, String.valueOf(BlockScore), 93, 107, Graphics.TOP| Graphics.LEFT, COLOR_WHITE, Font.SIZE_MEDIUM);
//畫當前戰況
for(int i=0;i
for(int j=1;j
if (Gridmatrix[i][j]!=0)
draw3DBlock(g,Gridmatrix[i][j],CANVAS_OFFSET_X+j*BLOCK_SIZE,
CANVAS_OFFSET_Y+i*BLOCK_SIZE,
BLOCK_SIZE,BLOCK_SIZE);
if (!ISDOWN){
//畫上新的方塊
LastX=BlockX; LastY=BlockY; LastType=BlockType;
for(int i=0;i<=3;i++)
draw3DBlock(g,BlockInfo[BlockType][8],
CANVAS_OFFSET_X+BlockX*BLOCK_SIZE+BlockInfo[BlockType][i*2+1]*BLOCK_SIZE,
CANVAS_OFFSET_Y+BlockY*BLOCK_SIZE+BlockInfo[BlockType][i*2]*BLOCK_SIZE,
BLOCK_SIZE,BLOCK_SIZE);
}
}
private boolean feasible(){
for(int i=0;i<=3;i++)
if (Gridmatrix[BlockY+BlockInfo[BlockType][i*2]][BlockX+BlockInfo[BlockType][i*2+1]]!=0)
return false;
return true;
}
private void delline(){
for(int i=0;i<=3;i++)
Gridmatrix[BlockY+BlockInfo[BlockType][i*2]][BlockX+BlockInfo[BlockType][i*2+1]]=BlockInfo[BlockType][8];
int temp=4;
boolean CanSkip=false;
int i=CANVAS_SIZE_HEIGHT-2;
while((temp>0)&&(i>=1)){
CanSkip=false;
label1: for(int j=1;j<=CANVAS_SIZE_WIDTH-2;j++){
if (Gridmatrix[i][j]==0) {CanSkip=true; i--; break label1;}
}
if (!CanSkip){
temp--;
for(int k=i;k>=1;k--)
for(int l=1;l<=CANVAS_SIZE_WIDTH-2;l++)
Gridmatrix[k][l]=Gridmatrix[k-1][l];
BlockLines++;
BlockScore+=200;
if((BlockScore%2000)<200) CurSpeed++;
}
}
}
public void run() {
while (Blocker != null) {
if(!ISDOWN){
BlockY++;
if (!feasible()) {
ISDOWN=true; BlockY--; delline();
try {Thread.sleep(400);} catch (InterruptedException e){}
}
else{
repaint();
try {Thread.sleep(950-100*(int)BlockSpeed);} catch (InterruptedException e){}
}
}
else{ BlockScore+=50;
if((BlockScore%2000)<50) CurSpeed++;
ISDOWN=false;
repaint();
BlockSpeed=CurSpeed;
BlockType=FutureBlockType;
FutureBlockType=Math.abs(generator.nextInt()%19);
BlockX=4; LastX=BlockX;
BlockY=0; LastY=BlockY;
if (!feasible()) { init();}
}
}
Blocker = null;
}
protected void keyPressed(int keyCode) {
//處理按下鍵盤的事件,這是Canvas的實例方法
switch (getGameAction(keyCode)) {//將按鍵的值轉化成方向常量
case Canvas.UP://向上
break;
case Canvas.DOWN://向下
BlockY++;
if (!feasible()) BlockY--;
repaint();
BlockSpeed=9;
//Blocker.run();
break;
case Canvas.LEFT://向左
BlockX--;
if (!feasible()) BlockX++;
break;
case Canvas.RIGHT://向右
BlockX++;
if (!feasible()) BlockX--;
break;
case Canvas.FIRE:
int tempBlockType=BlockType;
if (BlockType==1) BlockType=-1;
else if (BlockType==3) BlockType=1;
else if (BlockType==5) BlockType=3;
else if (BlockType==6) BlockType=5;
else if (BlockType==10) BlockType=6;
else if (BlockType==14) BlockType=10;
else if (BlockType==18) BlockType=14;
BlockType++;
if (!feasible()) BlockType=tempBlockType;
break;
default:
break;
}
repaint(); return;
}
}
參考
J2MEWTK 2.2 documentation.