目前手机网络游戏的迅速崛起,让游戏运营商们又发现了一个新的发展“特
区”,期待着手游能继网游之后成为新的经济增长点。手游为广大手机用户带来了
一种全新的休闲方式,他们可以随时随地利用手游放松紧张的情绪,缓解学习、工
作、生活中的种种压力。本章将介绍如何使用Java的 J2ME技术开发一款以象棋为
核心的手机网络游戏。通过阅读本章,可以学习到:
J2ME 程序开发
HTTP
的有状态连接
J2ME 与 Tomcat 的通信
如何绘制游戏界面
游戏信息处理
使用 EclipseMe 工具
手机网络游戏
(J2ME+Servlet 实现)
10第 章
Java 项目开发全程实录
10.1 开发背景
加入WTO之后,随着 3G时代的即将到来,中国手机网络游戏迅速崛起,成为继网游之后又一
新的经济增长点。×××有限公司是一家手机游戏运营商,公司已经从网络版的手机游戏运营中获取了大
量的资金,现在
了玩家的反馈信息,发现游戏玩家需要一些小型的休闲类游戏,例如纸牌、象棋、
围棋和一些智利游戏等。现需要委托其他单位开发一个网络版的手机象棋游戏。
10.2 系统
10.2.1 需求分析
通过与×××有限公司的沟通和需求分析,要求系统具有以下功能。
系统操作简单,界面友好。
运行速度快,保证程序的稳定性。
支持多人操作。
支持移动网络连接。
以HTTP协议通信。
10.2.2 可行性分析
从手机游戏依托的技术平台来看,目前 Java手机游戏保持很高的增长速度。手机游戏将是未来游戏
市场的主要发展方向,只要拥有一部手机,就可以进入到全新的掌上游戏世界。游戏正成为无线增值服
务的主力军,各游戏开发商早已洞察到这一点。通信公司 2.5G和 3G通信技术的发展,给手机用户带来
了高达每秒 384K6ps的移动带宽,将语音、图像、视频有序地结合起来,给手机用户带来了更加丰富多
彩的多媒体娱乐服务。借此东风,各游戏开发商将进入全新的无线网络游戏时代,市场潜力巨大。
手机版象棋游戏采用MIDP 1.0开发,在低端手机上运行的同时,保证了高端手机的兼容性,其
市场发展空间巨大。伴随着中国移动 GPRS和中国联通 CDMA1X数据业务的开展,手机游戏将是 3G
数据业务一个重要的应用领域。
·198·
第 10 章 手机网络游戏(J2ME+Servlet 实现)
10.3 系统设计
10.3.1 系统目标
根据需求分析的描述以及与用户的沟通,现制定系统实现目标如下。
界面设计简洁、友好、美观大方,保证直接上手便可游戏。
操作简单、快捷方便。
规则简单,方便游戏者进行游戏。
实现智能规则判断。
支持大型Web服务器,以Http协议通信。
向 PC端开发靠拢,为以后 PC与手机互联奠定基础。
10.3.2 系统功能结构
本系统的服务器端包括消息接收和桌面管理,功能结构如图 10.1 所示。客户端包括主窗体、游戏界
面、消息处理等模块,其功能结构如图 10.2 所示。
主机服务器端
消息接收
登
录
信
息
处
理
落
座
信
息
处
理
开
始
信
息
处
理
走
棋
信
息
处
理
退
出
信
息
处
理
桌面管理
更
新
单
个
桌
面
获
取
桌
面
列
表
更
新
所
有
桌
面
手机客户端
主窗体
桌
面
更
新
处
理
落
座
信
息
处
理
获
取
玩
家
列
表
落
座
消
息
处
理
退
出
信
息
处
理
游戏界面
绘
制
棋
盘
规
则
处
理
绘
制
棋
子
消息处理
消
息
发
送
图 10.1 服务器端系统功能结构 图 10.2 客户端系统功能结构
10.3.3 构建开发环境(根据语言的实际情况写)
在开发手机游戏时,分别使用了以下软、硬件环境。
硬件平台:
CPU:P 800GHzⅢ 。
内存:256MB以上。
硬盘:500MB以上空间。
显卡:32MB以上显存。
·199·
Java 项目开发全程实录
软件平台:
操作系统:Windows 2003(SP1)。
Java开发包:JDK 1.6。
J2ME开发包:Wireless Toolkit 2.5.2 for CLDC。
分辨率:最佳效果 1024×768像素。
手机分辨率:最佳效果为 240×292像素。
开发工具:Eclipse 3.2+MyEclipse 5.1+EclipseMe 1.7.7。
10.3.4 系统预览
手机版象棋游戏的界面根据具体游戏进度而变换,下面仅列出几个典型界面的预览,其他页面参
见光盘中的源程序。
游戏的开局界面如图 10.3 所示,该界面是游戏的主界面,包含游戏的规则算法、控制走棋、吃棋、
选棋、退出、开始等操作。如图 10.4 所示是游戏进行到死局的界面效果,在该界面中,红棋已经无路可
走,它被对方的“炮”和“车”将死。
·200·
第 10 章 手机网络游戏(J2ME+Servlet 实现)
图 10.3 开局界面(光盘\…\GameCanvas.java) 图 10.4 死局界面(光盘\…\GameCanvas.java)
输棋的界面如图 10.5 所示,该界面在玩家输棋的时候提示玩家“抱歉,您失败了”。游戏胜利界
面如图 10.6 所示,该界面在玩家取得胜利的时候提示玩家“恭喜,您获胜了”。
图 10.5 输棋界面(光盘\…\GameCanvas.java) 图 10.6 胜利界面(光盘\…\GameCanvas.java)
说明:由于路径太长,因此省略了部分路径,省略的路径是“TM\10\xiangqi\src\com\lzw”。
10.3.5 文件夹组织结构
在进行系统开发之前,需要规划文件夹组织结构。也就是说,建立多个文件夹,对各个功能模块
进行划分,实现统一管理。这样做的好处在于:易于开发、管理和维护。开发本游戏时,服务器文件夹
组织结构和客户端文件夹组织结构分别如图 10.7和图 10.8 所示。
·201·
Java 项目开发全程实录
图 10.7 服务器端文件夹组织结构
图 10.8 客户端文件夹组织结构
10.4 主程序设计
手游程序的主程序,必须继承 MIDlet类,MIDlet是一个抽象类,该类是手游应用程序的入口,
这和 Java应用程序中含有main()方法的主类相似。
Game类继承了MIDlet类成为手游程序的主类,并实现了 CommandListener接口处理相应的软键
触发事件。该类在程序界面中创建了 List 列表,该列表包括游戏桌面信息和相应的操作(本实例主要
是介绍游戏的开发,游戏桌面信息使用简单的字符信息“0”和“1”代替“空座”和“落座”信息)。
简单的界面如图 10.9 所示。
·202·
第 10 章 手机网络游戏(J2ME+Servlet 实现)
第一位数字代表桌子
号码,第二位和第三
位的 0 代表是否有玩
家落座
图 10.9 简单桌面列表信息
创建主程序的步骤如下:
(1)创建 Game类,该类必须继承 MIDlet类并实现 CommandListener接口。在类中创建并初始化
程序画布类(即游戏界面)、消息处理、桌面列表、软键按钮等对象。关键代码如下:
例程 01 代码位置:光盘\TM\10\xiangqi\src\com\lzw\Game.java
public class Game extends MIDlet implements CommandListener {
private Client client; //声明客户端消息处理对象
public static Display display; //声明静态的设备对象
private GameCanvas canvas; //声明游戏画布对象
private List playerList; //声明列表控件对象
private int[][] desks; //声明桌面数组
private int trySeat, tryDesk; //声明落座的桌号和位置变量
private Command okCommand; //声明落座按钮控件对象
public Game() { //在构造方法中初始化所有类成员
display = Display.getDisplay(this);
playerList = new List("Online player", Choice.EXCLUSIVE);
Command exitCommand = new Command("退出", Command.EXIT, 0);
playerList.addCommand(exitCommand);
okCommand = new Command("落座", Command.OK, 0);
playerList.addCommand(okCommand);
playerList.setCommandListener(this);
display.setCurrent(playerList);
client = new Client(this);
}
…//省略部分代码
}
(2)实现MIDlet类规定的 3个声明周期方法,其中 startApp()方法在手游程序被加载时,初始化
程序需要的数据, pauseApp()方法在手游程序被暂停或者被手机来电中断时暂存游戏数据,
destroyApp()方法在退出手游程序时负责清空程序数据,释放所有资源。这些方法是父类的抽象方法,
即使不实现方法逻辑,也要编写相应的方法声明。关键代码如下:
·203·
Java 项目开发全程实录
例程 02 代码位置:光盘\TM\10\xiangqi\src\com\lzw\Game.java
protected void startApp() throws MIDletStateChangeException {
}
protected void pauseApp() {
}
protected void destroyApp(boolean p0) throws MIDletStateChangeException { //在程序退出时,发送 exit消息
client.sendMessage("exit");
display.setCurrent(null);
}
(3)实现 CommandListener接口中规定的 commandAction()方法,该方法用于处理软键的触发事
件。当玩家触发“退出”按钮时,该方法将使游戏退出并结束手游程序。当玩家触发“落座”按钮时,
该方法将获取桌面列表中选择的当前列表项,并分析该桌面的玩家信息,如果该桌面有空位置,则设
置游戏玩家的桌号和座位号,并显示游戏界面。关键代码如下:
例程 03 代码位置:光盘\TM\10\xiangqi\src\com\lzw\Game.java
public void commandAction(Command c, Displayable s) {
if (c.getCommandType() == Command.EXIT) { //当玩家触发退出按钮时
client.sendMessage("exit");
try {
destroyApp(false);
notifyDestroyed(); //退出并结束手游程序
} catch (Exception e) {
e.printStackTrace();
}
} else if (c == okCommand) { //当玩家触发落座按钮时
if (playerList.getSelectedIndex() >= 0) {
try {
String info = playerList.getString(playerList
.getSelectedIndex()); //获取桌面信息
int index1 = info.indexOf("-");
int d = Integer.parseInt(info.substring(0, index1));
int index2 = info.indexOf("-", index1 + 1);
int d1 = Integer.parseInt(info
.substring(index1 + 1, index2));
int d2 = Integer.parseInt(info.substring(index2 + 1));
if (d1 == 0 || d2 == 0) { //如果该桌面有空的座位
if (d1 == 0)
trySeat = 0; //设置玩家座位号为 0
else
trySeat = 1; //或者设置玩家座位号为 1
tryDesk = d;
if (canvas == null)
canvas = new GameCanvas(this, client); //初始化游戏界面的画布对象
else
canvas.init(); //调用游戏界面的 init()方法
client.sendMessage("take," + d + "," + trySeat); //调用 sendMessage()方法发送落座消息
·204·
第 10 章 手机网络游戏(J2ME+Servlet 实现)
}
} catch (Exception exc) {
System.out.println("Error parseInt");
exc.printStackTrace();
}
}
}
}
(4)编写 takeSeat()方法,该方法用于处理玩家的落座信息。当程序接收到服务器的 takeseat信息
时,将调用该方法完成程序界面的初始化、设置玩家座位、桌号,并显示游戏界面。关键代码如下:
例程 04 代码位置:光盘\TM\10\xiangqi\src\com\lzw\Game.java
public void takeSeat() { //处理落座信息的方法
if (canvas == null) //如果游戏界面对象未初始化
canvas = new GameCanvas(this, client); //初始化游戏界面对象
else
canvas.init(); //调用游戏界面的 init()方法
canvas.setSeatPos(trySeat); //设置玩家座位
canvas.setDeskIndex(tryDesk); //设置玩家卓号
display.setCurrent(canvas); //显示游戏界面
}
(5)编写 updateDesk()方法,该方法用于更新桌面列表的单个列表项,也就是单个桌面的座位信
息。当程序接收到服务器的 updatedesk信息时,将调用该方法解析 updatedesk信息之后的桌面数据,
并更新到程序界面中。关键代码如下:
例程 05 代码位置:光盘\TM\10\xiangqi\src\com\lzw\Game.java
public void updateDesk(String str) { //更新桌面
int index1 = str.indexOf(","); //解析分隔符的位置
int index2 = str.indexOf(":", index1 + 1);
int index3 = str.indexOf(",", index2 + 1);
try { //截取桌面数据到 desks数组中
int d = Integer.parseInt(str.substring(index1 + 1, index2));
desks[d][0] = Integer.parseInt(str.substring(index2 + 1, index3));
desks[d][1] = Integer.parseInt(str.substring(index3 + 1));
playerList.set(d, d + "-" + desks[d][0] + "-" + desks[d][1], null); //更新桌面列表
} catch (Exception exc) {
}
}
(6)编写 setDesks()方法,该方法用于设置整个桌面列表。当程序接收到服务器的 desks信息时
(该信息包括了所有桌面信息),setDesks()方法必须解析这些信息并更新游戏主程序的所有桌面。关
键代码如下:
例程 06 代码位置:光盘\TM\10\xiangqi\src\com\lzw\Game.java
public void setDesks(String string) {
for (int i = 0; i < playerList.size(); i++) //清除原桌面列表
·205·
Java 项目开发全程实录
playerList.delete(i);
int index1, index2, index3, index4, index0; //解析分隔符位置
index1 = string.indexOf(",");
index2 = string.indexOf(":", index1 + 1);
int desknum = Integer.parseInt(string.substring(index1 + 1, index2)); //解析桌面编号
desks = new int[desknum][4]; //初始化桌面数组
index0 = index2;
int counter = 0;
while (counter < desknum) { //解析服务器发送的桌面列表信息
index1 = string.indexOf(",", index0 + 1);
index4 = string.indexOf(":", index1 + 1);
desks[counter][0] = Integer.parseInt(string.substring(index0 + 1,
index1)); //将解析后的数据存入数组中
if (index4 > 0)
desks[counter][1] = Integer.parseInt(string.substring(
index1 + 1, index4));
else {
string = string.trim();
desks[counter][1] = Integer.parseInt(string
.substring(index1 + 1));
}
playerList.append(counter + "-" + desks[counter][0] + "-" //将数组中的数据更新到主程序界面
+ desks[counter][1], null);
index0 = index4;
counter++;
}
}
10.5 公共模块设计
本系统的服务器项目空间中,有部分程序是公用的,它们被多个模块甚至整个系统重复调用完成
指定的业务逻辑,本节将这些公共的模块提出来进行单独介绍。
10.5.1 创建 Player 公共类
Player类代表玩家对象。每个玩家都有不同的属性,这些属性用来表示唯一的玩家。在现实社会中
没有完全相同的两个人物,而游戏中也应如此,否则无法保证游戏的数据安全。要做到区分不同的玩
家,就必须为每个玩家添加 IP地址和端口属性。另外,玩家会坐在不同桌子的不同座位上、有不同颜
色的棋子、独立的消息队列和开始状态等,这些都需要声明对应的属性并记录属性状态。关键代码如下:
例程 07 代码位置:光盘\TM\10\XiangQiServer\src\com\lzw\Player.java
public class Player {
private int ID = 1; //玩家编号
private String IP = ""; //玩家 IP地址
·206·
第 10 章 手机网络游戏(J2ME+Servlet 实现)
private int PORT = 9999; //玩家端口号
private Desk desk; //玩家所在的桌面对象
public Queue data; //消息队列
private String Color = ""; //玩家手中象棋的颜色
public boolean start = false; //开始状态
public Player(String ip, int p){
IP = ip; //初始化 IP
PORT = p; //初始化端口
desk = null;
data = new Queue(); //初始化消息队列
}
public void setDesk(Desk d) { //设置玩家桌号的方法
desk = d;
}
public Desk getDesk() { //获取玩家桌号的方法
return desk;
}
public boolean equals(String ip) { //判断是否同一个 IP玩家的方法
if (IP.equals(ip))
return true;
else
return false;
}
public boolean equals(Player p) { //判断是否同一个玩家对象的方法
if (IP.equals(p.getIP()) && PORT == p.getPort())
return true;
else
return false;
}
public String getIP() { //获取 IP的方法
return IP;
}
public int getPort() { //获取端口号的方法
return PORT;
}
…//省略部分代码
public boolean isStart() { //判断玩家是否开始游戏的方法
return start;
}
public void init() { //玩家的初始化方法
start = false; //设置未开始游戏状态
data.clear(); //清除消息队列
IP="";
PORT=0;
}
public void reset() { //玩家复位方法
Color = "";
start = false;
·207·
Java 项目开发全程实录
}
public String getColor() { //获取玩家象棋颜色的方法
return Color;
}
public void setColor(String color) { //设置玩家象棋颜色的方法
Color = color;
}
}
10.5.2 创建 Queue 公共类
编写消息队列公共类 Queue,该类负责存储服务器发送给玩家的消息,当手机客户端发出请求信
息时,服务器会从该客户端对应的玩家对象的消息队列中获取消息,如果消息队列中没有消息,那么
手机客户端必须等待服务器为其分配消息。
Queue公共类遵循“先进先出”的存储结构,数据元素只能从队尾进入,从队首取出。在队列中,
数据元素可以任意增减,但数据元素的次序不会改变。每当有数据元素从队列中被取出,后面的数据
元素依次向前移动一位。所以,任何时候从队列中读到的都是队首的数据。关键代码如下:
例程 08 代码位置:光盘\TM\10\XiangQiServer\src\com\lzw\Queue.java
public class Queue extends java.util.Vector { //继承向量实现集合类
public class EmptyQueueException extends java.lang.RuntimeException { //创建自定义异常类
public EmptyQueueException() {
super(); //调用父类的构造方法
}
}
public Queue() { //Queue公共类的构造方法
super();
}
public synchronized void push(Object x) { //添加队列消息的方法
super.addElement(x);
}
public synchronized Object pop() { //获取队列消息的方法
/* 队列若为空,引发 EmptyQueueException 异常 */
if (this.empty())
throw new EmptyQueueException(); //抛出异常的方法
Object x = super.elementAt(0); //获取第一个消息
super.removeElementAt(0); //从队列中移除第一个消息
return x;
}
public synchronized Object front() { //读取队首消息的方法
if (this.empty())
throw new EmptyQueueException();
return super.elementAt(0);
}
·208·
第 10 章 手机网络游戏(J2ME+Servlet 实现)
public boolean empty() { //判断队列消息是否为空的方法
return this.isEmpty();
}
public synchronized void clear() { //清除队列消息的方法
super.removeAllElements();
}
public int search(Object x) { //搜索消息的方法
return super.indexOf(x);
}
}
代码贴士
push(Objectx):向队列插入一个值为 x 的元素。
pop():从队列中取出一个元素。
front():从队列中读一个元素,但队列保持不变。
empty():判断队列是否为空,空则返回 true。
clear():清空队列。
search(x):查找距队首最近的元素的位置,若不存在,返回-1。
10.5.3 创建 Umpire 公共类
服务器端的Umpire公共类用于裁判游戏输赢、记录棋盘数据。在Desk公共类中将调用Umpire类
的moveChess()方法更新棋盘数据的记录。另外该公共类还定义了其他方法来更改棋盘数据,例如 init()
方法可以初始化棋盘数据到开局状态。关键代码如下:
例程 09 代码位置:光盘\TM\10\XiangQiServer\src\com\lzw\Umpire.java
public class Umpire {
private int huihe; //记录回合
private int bigID; //记录先手玩家的编号
private int score; //记录分数
protected static int i, j; //棋盘数组的下标编号
protected static int isRedWin = 1; //判断红色玩家的胜利记录
protected static int isWhiteWin = 1; //判断白色玩家的胜利记录
private int point[][]; //声明棋盘记录数组
public Umpire() {
huihe = 0; //初始化回合记录
score = 0; //初始化分数记录
point = new int[][] { { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, //初始化棋盘数据记录
{ 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 10, 0, 0, 0, 0, 0, 11, 0 },
{ 12, 0, 13, 0, 14, 0, 15, 0, 16 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 28, 0, 29, 0, 30, 0, 31, 0, 32 },
{ 0, 26, 0, 0, 0, 0, 0, 27, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 17, 18, 19, 20, 21, 22, 23, 24, 25 } };
}
·209·
Java 项目开发全程实录
public void checkWin() { //判断输赢的方法
isRedWin = 0;
isWhiteWin = 0;
for (i = 0; i < 3; i++) { //遍历“将”的活动范围
for (j = 0; j < 3; j++) {
if (point[0 + i][3 + j] == 5) { //如果失去主将
isRedWin++; //则另一方胜利
}
}
}
for (i = 0; i < 3; i++) { //遍历“帅”的活动范围
for (j = 0; j < 3; j++) {
if (point[7 + i][3 + j] == 21) { //如果失去主帅
isWhiteWin++; //则对方胜利
}
}
}
}
public void moveChess(int selectedY, int selectedX, int n, int m) { //记录走棋的方法
point[selectedY][selectedX] = point[n][m]; //更新棋盘指定位置的数据
point[n][m] = 0; //移动棋子之后,将原位置清空
checkWin(); //调用判断输赢的方法
}
public void logHuihe() { //记录回合数的方法
huihe++;
}
public int getHuihe() { //获取回合数的方法
return huihe;
}
public void init() { //初始化方法,在开始新局游戏时调用
huihe = 0; //回合归零
score = 0; //分数归零
isRedWin = 1; //初始化两方胜利记录
isWhiteWin = 1;
point = new int[][] { { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, //初始化棋盘数据
{ 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 10, 0, 0, 0, 0, 0, 11, 0 },
{ 12, 0, 13, 0, 14, 0, 15, 0, 16 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 28, 0, 29, 0, 30, 0, 31, 0, 32 },
{ 0, 26, 0, 0, 0, 0, 0, 27, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 17, 18, 19, 20, 21, 22, 23, 24, 25 } };
}
…//省略部分代码
}
代码贴士
checkWin():该方法分别搜索游戏双方的主将,如果有一方失去主将,则另一方胜利。
·210·
第 10 章 手机网络游戏(J2ME+Servlet 实现)
� moveChess():该方法将根据用户的操作更改棋子的位置,然后调用 checkWin()方法,判断此次走棋是否导致
另一方失败。
� init():该方法用于初始化新一局游戏。它将执行清除胜负记录、回合数、复位棋盘操作。
注意:在程序中所描述的白色象棋棋子在后期美工设计时,为使界面美观,被设计成黑色的棋子。
10.5.4 创建 Desk 公共类
服务器端的Desk公共类是游戏桌面的定义类,该类包含了游戏桌面的编号、玩家数量、游戏回合
局数等属性,另外该类还包括Umpire 对象、Player 对象、Server 对象等属性,其中 Server 对象是服务器
的主程序,它负责信息的接收、发送和处理等业务。Desk公共类的属性声明与初始化的关键代码 如
下:
例程 10 代码位置:光盘\TM\10\XiangQiServer\src\com\lzw\Desk.java
public class Desk {
private int ID; //桌子 ID
private Player[] players; //玩家数组
private int NUM = 2; //玩家数量
private Player banker = null; //先手
private int bankerID = 0; //先手 ID
private Umpire umpire; //棋盘记录
private int game = 0; //游戏局数
private int score = 0; //分数
Server server; //服务器信息处理对象
public Desk() {
game = 0; //初始化游戏局数
server = new Server(); //初始化信息处理对象
umpire = new Umpire(); //初始化棋盘记录对象
players = new Player[NUM]; //初始化玩家数组
banker = null; //初始化先手
bankerID = 0; //初始化先手编号
for (int i = 0; i < NUM; i++) {
players[i] = null; //初始化同一桌面的玩家
}
}
public void init() { //桌面的初始化方法
banker = null;
bankerID = 0;
game = 0;
umpire.init(); //初始化棋盘记录对象
}
Desk公共类将控制游戏的初始化、开始、重新开始、游戏结束、走棋等游戏中的业务逻辑,这些业务
逻辑有不同的方法实现。关键代码如下:
·211·
Java 项目开发全程实录
例程 11 代码位置:光盘\TM\10\XiangQiServer\src\com\lzw\Desk.java
public void reset(){ //桌面复位方法
umpire.init();
for(int i=0;i
= NUM)
return false;
return players[pos] == null;
}
public int getPlayerSeat(Player p) //返回玩家座位
{
for (int i = 0; i < NUM; i++) {
if (players[i] == null)
continue;
if (players[i].equals(p)) //判断指定玩家和座位上的玩家是否为同一个玩家
return i;
}
return -1;
}
public void setPlayer(int pos, Player n) { //设定玩家 n 坐在 pos 座位上
if (pos >= NUM)
return;
players[pos] = n;
}
public void removePlayer(Player p) { //移除玩家 p
for (int i = 0; i < NUM; i++) {
if (players[i] == null)
continue;
else if (players[i].equals(p))
players[i] = null;
}
}
Desk公共类在管理游戏进度、规则、走棋记录等操作时,需要向玩家发送通知信息,这些信息需要
分别发送给所有玩家、单个玩家和除自己以外的其他玩家。这就需要编写不同的消息发送方法。关键代
码如下:
例程 13 代码位置:光盘\TM\10\XiangQiServer\src\com\lzw\Desk.java
public void sendMessageToAll(String mes) { //发送信息到所有玩家
for (int i = 0; i < NUM; i++)
if (players[i] != null)
sendMessage(players[i], mes);
}
public void sendMessageToOther(Player player, String message) { //发送信息到其他玩家
for (int i = 0; i < NUM; i++) {
if (players[i] != null && !players[i].equals(player)) //判断接收信息的玩家是否是自己
·213·
Java 项目开发全程实录
sendMessage(players[i], message);
}
}
public void sendMessage(Player p, String m) { //发送信息到单个玩家的方法
server.sendMessage(p, m);
}
public void sendBankerInfo() { //发送象棋先手信息
sendMessageToAll("bankerInfo:" + bankerID);
}
}
10.6 游戏模块设计
10.6.1 游戏模块概述
手机的游戏模块包括界面设计、规则算法、按键处理等。对于手机游戏来说,界面要根据游戏所针
对的客户群体来设计,只有使客户群体接受游戏界面,游戏产品才能拥有更好的卖点。例如,针对白
领人士,需要制作适合 30岁以上的,集稳重、智慧、精干于一体的有些类似办公的界面,界面要简洁,
不需要太多的颜色装饰。规则算法是游戏公平和完整性的保障。按键处理负责游戏中的具体操作,例如
走棋、选棋等。游戏模块的界面如图 10.10 所示。
图 10.10 游戏界面
10.6.2 游戏模块技术分析
Canvas是MIDP 提供的低级用户界面类。和高级用户界面相比,Canvas拥有更大的灵活性。由于
Canvas 不提供任何现成的可视组件,所以要在 Canvas上显示图形或者文本,都必须通过 Graphics类
·214·
第 10 章 手机网络游戏(J2ME+Servlet 实现)
绘制出来。如果使用高级用户界面,不但界面死板无法更改,而且也缺少相应的灵活性,不能用于开
发游戏。因此,使用低级用户界面,开发人员可以获得完全的界面控制能力,并能精确地控制每一个
像素的位置,在游戏开发中这是必不可少的。Canvas类定义的常用方法如表 10.1 所示。
表 10.1 Canvas 类定义的常用方法
方 法 名 称 说 明
paint 绘图方法,用于绘制游戏界面
repaint 重新执行绘图方法
isDoubleBuffered 判断是否支持双缓存技术
getKeyCode 获取指定动作对应的按键代码
10.6.3 棋盘绘制模块实现过程
(1)游戏界面中最重要的就是棋盘界面,包括棋盘和棋子的大小、颜色、棋子布局位置等多个属
性,所以定义棋盘绘制模块的属性是首要任务。创建 GameCanvas类,该类必须继承 Canvas类成为游
戏界面的画板,游戏中的所有事务,都是在这个画板上绘制的。另外,该类还需要实现
CommandListener接口来处理软键按钮的事件处理,在该类中声明游戏中需要的各种属性。关键代码如
下:
例程 14 代码位置:光盘\TM\10\xiangqi\src\com\lzw\GameCanvas.java
public class GameCanvas extends Canvas implements CommandListener {
protected Game game; //游戏主类对象
String color = "";
protected int rightSpace; //屏幕右侧预留的空间
protected int x; //棋盘输出的坐标
private boolean myTurn = false;
protected int gridWidth; //每个棋格的边长
protected int mapWidth, canvasW; //棋盘的宽度和画布的宽度
protected int a, b, c, d;
protected int chessR; //棋子的半径
private int desknum = -1; //桌子序号
private int seatPos = -1; //座位序号
private boolean banker = false;
protected int selectedX, selectedY; //选择框在棋盘格局上的 x,y位置
protected static int i, j;
protected int m, n, p; //记录开始的选择框位置和在数组中的位置
protected String q;//记住word[selectedX][selectedY]
protected int guard, guard1, guard2, g, g1; //标记 FIRE 被按了多少次
protected static int turnWho; //表示该谁走了
protected static int isRedWin; //红棋胜利
protected static int isWhiteWin; //白棋胜利
private Client client; //消息管理对象
·215·
Java 项目开发全程实录
protected Command exitCmd, start, ok; //软键按钮
private int point[][]; //棋子位置数组
protected String[][] chess; //棋子名称数组
private int chessSelColor; //选择棋子的颜色
private int backColor; //棋盘背景色
private int charColor; //棋子汉字颜色
private int lineColor; //棋盘线的颜色
private int borderColor; //楚河汉界的颜色
private int selBorderColor; //选择棋子的边框色
private int blackChees; //黑棋