1.
单机游戏直接【开始】
就行了
2.
联机游戏
①
A
方游戏玩家建立游戏服务器端等待另一方加入游戏后才可
以开始游戏
②
B
方通过
A
方的
IP
加入
A
方创建的游戏,加入成功后
B
方能看
到加入成功的提示
③
B
方加入成功后
A
方看到
B
方已经加入了游戏,此时
A
方可以【开始】游戏,这样
A B
方就能同步地玩游戏了

v
游戏用例图
v
整个游戏要实现的功能

类图
(
聚合关系
)

类图
(
继承实现关系
)

设计要点
v
实现两机互连
v
矩阵
à
游戏界面
v
方块移动和变形算法
v
键盘控制
v
游戏同步
v
游戏网络协议
v
菜单控制
实现两机互连
v
MyServer
类:继承的
Thread
类,在线程的
Run
()方法里面建
立
ServerSocket
进行监听,这样做的目的是在没有客户连接请
求时可以做其它的事情.负责接受客户端请求的套接字,然后
获得按受到的套接字的输入流和输出流.输入流的读取交给了
ReadThread
线程.
ReadThread
线程取得输入流中的数据再让
网络接口(自定义的接口,主要负责解析数据类型)去解析数
据.

MyClient
类:负责与服务器连接,指定
IP
,指定端口,同样要在连接成功之后,获得
Socket
的输入流和输出流.输入流的读取交给了
ReadThread
线程.
ReadThread
线程取得输入流中的数据再让网络接口(自定义的接口,主要负责解析数据类型)去解析数据.
矩阵
-->
游戏界面
v
private int[ ][ ] m_nField 10
列
20
行
v
在
Java
动画技术里经常会使用到双缓冲。双缓冲技术是一种很有效的避免闪烁的方
法。它的特点是:不是直接在屏幕上画图,而是对内存里的一个虚拟的图片进行操
作,当复杂的画图过程结束后,直接将内存里的这张图,贴到屏幕上就行了。这样
做的好处在于对屏幕操作的时间缩短,这样对图像的闪烁感就会减少。
v
双缓冲的实现分两步:
v
①首先建立一个
Image
对象
v
Image img=createImage(width,height);
v
在
Java
里
Image
类是一个虚类,不能直接创建出一个新的对象,不过可用
Java
里的
createImage(int w,int h)
创建出宽为
w
高为
h
的图片图像。
v
②获取
Image
对象的
Graphics
对象,通过这个对象就可以在
Image
对象上画需要的图
形。
v
Graphics g=img.getGraphics();
v
在
g
上画图就相当于在
Image
上画图。
v
③画好图后,将
img
对象直接贴到游戏界面上
v
public void paint(Graphics gg)
v
{
v
gg.draw(img,0,0,this);
v
}
|
0
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
0 | ||||||||||
1
| ||||||||||
2
| ||||||||||
3
| ||||||||||
4
| ||||||||||
5
| ||||||||||
6
| ||||||||||
7
|
m_nField[20][10]
| |||||||||
8
| ||||||||||
9
| ||||||||||
10
| ||||||||||
11
| ||||||||||
12
| ||||||||||
13
| ||||||||||
14
| ||||||||||
15
| ||||||||||
16
| ||||||||||
17
| ||||||||||
18
| ||||||||||
19
|
方块的定义
v
在游戏里操纵的是一个个方块,应该编写一个类来封装这
些方块。
v
方块类的设计:
v
一个方块有三个属性
:X
坐标,
Y
坐标和颜色
v
int m_nColumn;
v
int m_nRow;
int m_nColor;
int m_nColor;
对于方块类还有一些方法:
v
判断两个方块是不是相等
public boolean IsEqual(Square s)
v
判断这个方块是不是还在游戏的可玩的区域里
public boolean
InBounds
(int colMax, int rowMax)
v
方块组的构成
形状索引
|
0
|
1
|
2
|
3
|
4
|
5
|
6
| |||||||||||||||||||||
形状
| ||||||||||||||||||||||||||||
1
|
0
|
2
|
3
|
| |
| |
| |
2
|
| |
| |
1
|
| |
2
|
| |
1
|
0
|
2
|
2
|
0
|
1
|
| |
|
| |
| |
0
|
3
|
| |
| |
3
|
| |
0
|
| |
3
|
0
|
| |
| |
3
|
3
|
| |
| |
| ||||||||||||||||||||||||||||
颜色索引
|
1
|
2
|
3
|
4
|
5
|
6
|
7
| |||||||||||||||||||||
颜色描述
|
红色
|
桔黄色
|
绿色
|
兰色
|
品红色
|
粉红色
|
青色
|
方块组移动和变形算法
v
方块组的移动算法,从一个方块组移动到另一个方块组,如果可以移
动,则返回真,并且修改
m_nField[][]
的值
,
不可以返回假
v
public boolean moveSquares(Square from[],Square to[])
v
{
v
/**
判断是否能移动
*/
v
outerlable:
v
for (int i = 0; i < to.length; i++)
v
{
v
/**
如果不在可玩区域,则返回假
*/
v
if (to[i].InBounds(m_nCols,m_nRows+4) == false)
v
return false;
v
if (m_nField[to[i].m_nColumn][to[i].m_nRow] != 0)
v
{
v
for (int j = 0; j < from.length; j++)
v
if (to[i].IsEqual(from[j]))
v
continue outerlable;
v
return false;
v
}
v
}
v
/**
移动到
to
的位置,清除
from
位置
*/
v
for (int i = 0; i < from.length; i++)
v
if (from[i].InBounds(m_nCols,m_nRows+4))
v
m_nField[from[i].m_nColumn][from[i].m_nRow] = 0;
v
for (int i = 0; i < to.length; i++)
v
m_nField[to[i].m_nColumn][to[i].m_nRow] = to[i].m_nColor;
v
this.playMusic("dead.wav");//
移动一次播放一次移动的声音
v
return true;
v
}
v
From
定义为没有移动之前的那个方块组,
to
定义为要移到的方块组。
v
定理:对于两个非零的向量
a,b,
如果
a
b=0,
那么
a
┴
b
;反之如果
a
┴
b
,那么
a
b=0
。
v
问题:已经向量
a=(X,Y)
要使
|b|=|a|
,且
b
┴
a,
求向量
b?
v
解:设向量
b(X1,Y1)
X2+Y2= X12+Y12
X*X1+Y*Y1=0
v
解出方程组的解为
x1=-y,y1=x;
或者
x1=y,y1=-x.
v
如果要实现方块的逆时针方向转动
取第一种解
: x1=-y,y1=x1203
逆时
针
v
0,1,2,3
表示
m_curPiece [0], m_curPiece [1], m_curPiece [2],
m_curPiece [3]
v
根据求解得出的公式
以
m_curPiece [0]
的坐标为相对坐标原点,依次求出
m_curPiece [1],
m_curPiece [2], m_curPiece [3]
的相对于
m_curPiece [0]
的坐标
int dx = m_curPiece[i].m_nColumn - m_curPiece[0].m_nColumn;
int dy = m_curPiece[i].m_nRow - m_curPiece[0].m_nRow;
v
那么
m_curPiece[i]
逆时针旋转
90
度后的相对于
m_curPiece[0]
的
坐标为
v
(-dy,dx)
那么
m_curPiece[i]
绝对坐标为
v
(m_curPiece[0].m_nColumn-dy, m_curPiece[0].m_nRow+dx)
v
则
m_curPiece[i]
旋转
90
度后的的新方块为
v
Square(m_curPiece[0].m_nColumn-dy,
v
m_curPiece[0].m_nRow+dx,
v
m_curPiece[i].m_nColor)
v
如果不旋转
左右移动则相应加或减去移动的步长
v
newpos[i] = new Square(m_curPiece[i].m_nColumn + nDx,
v
m_curPiece[i].m_nRow + nDy,
v
m_curPiece[i].m_nColor)

v
/**
v
*
移动方块
v
*@param nDx
左右移动,向左为
-1
向右为
1
不左右为
0
v
*@param nDy
上下移动,向下为
-1
不上下为
0
v
*@param bRotate
是否转动
true
为转动
v
*/
v
public synchronized boolean moveCurPiece(
v
int nDx,
v
int nDy,
v
boolean bRotate)
v
{
v
Square newpos[] = new Square[4];
v
for (int i = 0; i < 4; i++)
v
{
v
if (bRotate) //
变形
v
{
v
int dx = m_curPiece[i].m_nColumn - m_curPiece[0].m_nColumn;
v
int dy = m_curPiece[i].m_nRow - m_curPiece[0].m_nRow;
v
newpos[i] = new Square(m_curPiece[0].m_nColumn - dy,
v
m_curPiece[0].m_nRow + dx,
v
m_curPiece[i].m_nColor);
v
}
v
else //
复制要移动到的位置
v
{
v
newpos[i] = new Square(m_curPiece[i].m_nColumn + nDx,
v
m_curPiece[i].m_nRow + nDy,
v
m_curPiece[i].m_nColor);
v
}
v
}
v
if (moveSquares(m_curPiece, newpos) == false)
v
//
不能移到指定位置返回
false
v
return false;
v
m_curPiece = newpos; //
能移返回
true
v
return true;
v
}
键盘控制
v
给游戏添加键盘侦听器
v
侦听器
KeyHandler
实现接口
KeyListener
v
重写
KeyListener
中的
keyPressed
这个方法
(
包含字母
键和非字母键
)
v
左移
m_tTetrics.moveCurPiece( -1, 0, false)
v
右移
m_tTetrics.moveCurPiece( 1, 0, false)
v
变形
m_tTetrics.moveCurPiece( 0, 0, true)
v
下降
v
while (m_tTetrics.moveCurPiece(0, -1, false));
游戏同步
v
要想实现游戏的同步①保证游戏双方所需要的方块形状出现的
次序一样②游戏双方实时地进行游戏区域数据的交互
v
实现方法
:
v
①游戏双方都有自己存放方块形状的缓冲区
v
有了缓冲区当然就要有生产和消费
,
生产者有游戏服务器端去做
,
生产的方块添加到自己缓冲区并同时发送给客户端
,
客户端端收
到后再添加到自己的缓冲区
v
②一方的方块组每移动一步就要把自己的区域信息发送给另一
方
,
另一方收到后
,
再更新对方的游戏区域矩阵
m_nRivalField[][]
数据
, m_nRivalField[][]
里数据变了
,
画出的图形也就变了
v
PieceBuffer
是一个具有互斥功能的环状缓冲区,用于
存放生产者生产出的游戏所需要的方块组索引值
v
方块组索引值加
1
后就是画图所用的颜色索引值
,
一值
两用

v
PieceBuffer
是一个具有互斥功能的环状缓冲区,用于
存放生产者生产出的游戏所需要的方块组索引值
v
方块组索引值加
1
后就是画图所用的颜色索引值
,
一值
两用
游戏网络协议
v
这样游戏的服务器端和客户端就能在连接成功之后相
互通信了.既然能够通信,那么双方之间通信的数据
到底是属于哪些数据?是游戏的状态数据,还是组织
游戏的动作命令,或者是双方聊天的内容?这就需要
事先归定发送的数据是属于什么数据,从协议的角度
出发,在数据内容头部加一个数据类型的报头,这样
另一方在收到数据时,只要剥取数据的头部,然后根
据头部的内容来判断数据类型,然后再决定怎样对待
这类型的数据.
v
在网络接口里定义了这些网络协议报文头常量
v
public final int STARTGAME=11;
/**
开始游戏
*/
v
public final int PAUSEGAME=12;
/**
暂停游戏
*/
v
public final int ENDGAME=13;
/**
结束游戏
*/
v
public final int EXITGAME=14;
/**
退出程序
*/
v
public final int GAMEDATA=21;
/**
游戏数据
*/
v
public final int GAMEOVER=22;
/**
游戏结束
*/
v
public final int GAMEREMOVELINE=23;/**
游戏消行
*/
v
public final int GAMESCORE=24;
/**
游戏分数
*/
v
public final int GAMEPIECE=25;
/**
游戏的
Piece*/
v
public final int TALK=31;
/**
聊天内容
*/
v
整个数据是一个字符串
v
字符串前
2
位是数据类型
v
如:"
31
"
表示该条数据是聊天内容
v
字符串第3位是一个分隔符冒号
”
:
”
隔开数据类型和数据内容
v
从第四位开始为数据内容
v
如
:str=”31
:
你好!
”
3
|
1
|
:
|
你好!
|
v
1位 2位 3位 4位~N位
菜单控制
菜单结构一览
主菜单项
|
子菜单项
|
描述
|
游戏
jMenuGame
|
开始
jMenuStartGame
暂停
jMenuPauseGame
结束
jMenuEndGame
关闭
jMenuExit
|
开始游戏
暂停游戏
结束游戏
退出程序
|
设置
jMenuSet
|
设置级别
jMenuGameLevel
创建游戏
jMenuCreateGame
加入游戏
jMenuAddGame
退出游戏
jMenuExitGame
|
创建
server
端等待
client
来连接
作为
client
端去连接
server
端
断开网络连接
|
关于
jMenuHelp
|
关于
jMenuSeeAbout
记录
jMenuSeeRecord
|
关于制作
查看记录分数
|
创建游戏时序图

开始游戏时序图
