通过java swing技术开发的俄罗斯方块游戏,实现了基础的游戏绘制、移动逻辑、消除逻辑、得分统计、死亡判定、预测方块等功能。
成果展示截图:
项目源码及 可执行文件jar 下载 :
https://github.com/echowz/Teris
游戏介绍:
俄罗斯方块的规则:
玩家不断操作下落的不同板块,直达底部,并刷新新的板块。在操作时通过调整位置和旋转方向,使得落下的方块可以填满一整层,每当一层填满,则该层会消失,上方所积累的方块会落下,同时积累分数,当累积到上方空间不足以刷新新的方块,且整个空间无法消除时,则游戏结束
游戏场地:10×20的平面网格地图。每个单位为相同宽度的小正方形。
操作图形:一组固定由4个小正方形组成的不同规则的图形,称为Tetromino。共有6种形态,通过字母象形分别记作: I, J, L, S, Z, O, T。
I:![](https://img-blog.csdnimg.cn/9e7905897ebf420baa025c360cc47271.png)
连续的4个方块组成一长条图形,形如字母I。一次最多可以消除4层。共有2种旋转状态。
J:
形如字母J,一次最多可以消除3层,共有4种旋转状态。
L:
形如字母L,J的对称形状,一次最多可以消除3层,共有4种旋转状态
O:
形如字母O,4个小正方形堆叠形成的大正方形,一次最多可以消除2层,仅有一种旋转状态。
S:
形如字母S,一次最多可以消除2层,共有2种旋转状态
T:
形如字母T,一次最多可以消除2层,有4种旋转状态。
Z:
形如字母Z,一次最多可以消除2层,共有2种旋转状态
分数累计: 一次消除层数的平方*10,1层为10分,2层为40分,3层为90分,4层为160分。
项目结构:
主框体程序结构:
GamePanel 即框体左侧的游戏本体部分,继承自模块Jpanel,负责绘制游戏平面显示。
Menu即框体右侧的菜单信息部分,继承自模块Jpanel负责展示信息。
MainFrame 为整个框体,继承自Jframe。
在Main 主类中负责创建和编写游戏流程等逻辑。
方块样式结构:
抽象类Tetromino描述了一个类型方块的基本特征,坐标,移动方法,旋转方法等。
cell类描述一个单独的小方块,记录坐标。
7个实现类都以 Cell 组成,记录其各个子小方块所处的位置,从而组成了整个方块的形态。
每个Cell方块都拥有其坐标属性
如 I 方块的组成可以表示为: 以其中一个靠中的方块(x,y)作为整体 I方块 的坐标表示,
生成 I 方块 对象时,将生成四个Cell对象来组成整体。此时,I 方块 的其中一个旋转状态便表示完毕,但每个类型的方块都可能有多个旋转状态,因此
项目介绍
框体部分:
GamePanel
该区域绘制整个版面,用成员变量二维数组graph记录10*20的网格中的信息,当网格的值为0时表示该网格为空,其他正值分别表示不同的颜色,若为-1则该网格为界外网格,也是不显示的网格。由于gamepanel只创建一次,其成员变量采用静态方法,方便全局调用。
成员变量部分:
public static int[][] graph; //地图的二维数组映射
public static Color[] colors; //7种方块7种颜色,便于区分和识别。
构造部分:
public GamePanel()
{
init();
setBackground(Color.CYAN); //背景色设为淡蓝 方格填充后作为淡蓝色网格线
setBounds(new Rectangle(0,0,MainFrame.width*2/3,MainFrame.height));//设置panel区域的长宽和位置,为整个框体的左三分之二处
setVisible(true);
}
public void init() //初始化辅助参数,颜色集,地图等。
{
colors=new Color[]{ //初始化颜色数组
Color.white, //默认静态final变量,返回的是白色的Color对象
new Color(0,240,240),
new Color(0,0,240),
new Color(240,160,0),
new Color(240,240,0),
new Color(0,240,0),
new Color(160,0,240),
new Color(240,0,0)};
graph = new int[25][15]; //初始化地图大小
for(int i=0;i<=22;i++)
Arrays.fill(graph[i], -1); //先全部初始化-1,再将合法区域初始化为0
for(int i=1;i<=20;i++) //地图的合法区域为x:1-20 y:1-10;
for(int j=1;j<=10;j++)
graph[i][j]=0; //经初始化,-1为出界。
}
重写paint方法,整个程序的核心绘制,整个版面的显示:
@Override
public void paint(Graphics g)
{
super.paint(g);
for(int i=1;i<=20;i++)
for(int j=1;j<=10;j++)
{
g.setColor(colors[graph[i][j]]);
int x=MainFrame.width/40+(j-1)*MainFrame.width/16;
int y=MainFrame.width/20+(i-1)*MainFrame.width/16;
int width=MainFrame.width/16-2,height=MainFrame.width/16-2;
g.fillRect(x,y,width,height);
}
}
Menu
menu区域用作信息显示,不作重点讲解,预测下一个方块的功能有一定代码逻辑。与Tetromino的构造结合应用。由于Menu只会创建一个,其成员变量采用静态方式。
成员变量:
public static int score; //当前分数
public static ArrayList<Image> imageList; //用于显示下一个方块的图片,打印在menu中
public static int currentPicture; //当前需要打印的图片为
构造方法中初始化信息,paint中绘制界面。
MainFrame
定义了整个程序需要参考的参数:宽、高
public static final int height=800;
public static final int width=height*3/4;
构造函数中设置框体的基本属性即可。
Main
主类中创建的全局变量为:
public static Tetromino select; //当前操控的方块设置为select
public static Menu menu;
public static GamePanel gamePanel;
public static MainFrame mainFrame;
public static final Object object = new Object(); //用作synchronized 参数,用于线程阻塞,循环下降操作的等待过程。
public static int pauseTime=500; //自动下落的间隔时间,用于控制速度,间隔越短下落速度越快。
在main方法中创建gamepanel、menu后添加到创建的mainframe中,并添加一个新线程用作不断重绘两个板块:gamepanel和menu中的内容。
public static void main(String[] args) {
System.out.println("运行开始");
gamePanel = new GamePanel();
menu = new Menu();
mainFrame = new MainFrame();//Mainframe需要在后,因为其中有需要调用到panel的成员的语句
mainFrame.add(gamePanel);
mainFrame.add(menu);
new Thread(()-> {
while (true) {
gamePanel.repaint();
menu.repaint();
}
}).start(); //持续刷新画布的线程。
updateTet();
Thread downs = new Thread(()->{ //不断循环下落的线程。
try {
while (true) {
synchronized (object) {
down();
object.wait(pauseTime/2); //下落速度
object.wait(pauseTime/2);
}
}
}
catch (Exception e) {
e.printStackTrace();
}
});
downs.start();
...
...
}
在main中添加键盘监听,完善游戏的操作逻辑。
mainFrame.addKeyListener(new KeyAdapter() { //监听玩家操作
@Override
public void keyPressed(KeyEvent e) {
switch (e.getKeyChar()) { //获取键盘活动的char值
case 'a': //左右移动的逻辑,当前选定的Tetromino方块 有抽象方法移动和旋转,移动传递参数01分别为左右移动。
case 'A':
select.move(0);
break;
case 'd':
case 'D':
select.move(1);
break;
case 's': //s用作单次点击加速下落
case 'S':
down();
break;
case ' ': //调用当前选定的Tetromino方块的自我旋转方法,实现旋转
select.spin();
break;
case 'w': //触发/取消 暂停下落
case 'W':
if (downs.isAlive())
synchronized (object) {
object.notify(); //下落速度
}
if (pauseTime == 500) pauseTime = 2000000;
else pauseTime = 500; //通过改变下落间隔实现暂停和继续的功能
break;
}
}
}
);
方块部分:
小方块Cell类
成员变量
即记录当前小方块所处坐标的x和y
public int x;
public int y;
成员方法
setColor() 将当前小方块所处的位置的单元格上色。
isBorder()是否处于边界(根据将要移动的方向来判断,即可能是边界,也可能是另一个已填充的方块)
legal()判断这个小方块当前的位置是否是合法的。(在染色前判断,该位置应该是空的才会被染色,因为俄罗斯方块中不存在覆盖颜色的行为,所有只有不出界且为空才合法);
public void setColor(int color) { //给当前小方块所在位置上色
GamePanel.graph[Math.max(0,x)][Math.max(0,y)]=color;
}
public boolean isBorder(int dir) { //参数 0向左移动,1向右移动 2向下 通过该小方块的移动判断整体移动后是否会撞击边界或其他方块 作为碰撞判定的一部分
if(dir==0) return GamePanel.graph[x][y-1] != 0;
else if(dir==1) return GamePanel.graph[x][y+1]!=0;
else if(dir==2) return GamePanel.graph[x+1][y]!=0;
return true;
}
public boolean legal(){ //判断该方块当前位置是否合法。 //合法返回真
if(x <1 || x > 20 || y <1 || y > 10)return false;
return GamePanel.graph[x][y]==0;
}
抽象类Tetromino
拥有的成员变量为:
public int color; //颜色
public int x; //当前方块的坐标
public int y;
public int status; //当前方块处于的旋转状态为
public int statusNum=4; //当前方块一定有几个状态
public static int currentTetromino=new Random().nextInt(7);//当前选定的方块,通过随机数选定。
public Cell[][] cells; //当前方块的i个状态,每个状态4个小方块,所记录的小方块信息。
//旋转时将在第一维,不同的状态之间切换; 移动时将在第二维,对当前状态下的4个方块进行遍历,查看是否可以移动,和修改其位置信息。
Tetromino的核心方法为move方法和spin方法,移动和旋转是俄罗斯方块的核心操作方式,是画面的主要改变方式。
move(int dir)接收参数,通过参数值的不同实现左右下三个方向的移动。遍历所含的4个小方块,假定朝既定方向移动,判断移动后的位置是否合法,如果都合法则实现移动,有不合法的则撤销。
spin()不接收参数,将当前操控的方块进行顺时针90°的旋转。
在实现层面上讲,是将当前选定方块切换至下一个旋转状态,因为每个方块都有成员cell【】【】,也就是每个旋转状态的每个小方块儿子属性,在旋转时遍历所含的小方块判定下个状态是否合法,合法则旋转。
public void move(int dir) { //0左 1右 2下
setColor(0); //将该方块移除
for(int j=0;j<4;j++)
if(cells[status%statusNum][j].isBorder(dir)) { //是否为底部,即下面一个单位是否有元素。
setColor(color);
return;
}
if (dir == 0) y--;
else if(dir==1) y++;
else if(dir==2) x++;
setCells(x,y);
setColor(color);
}
public void spin() { //旋转
setColor(0); //实现逻辑为先假设旋转,先移除旧的,再切换状态,然后判断旋转后的方块所处位置是否合法(全为0)。如果合法则应用填色,不合法则恢复
status++; //下一个状态
for (int j = 0; j < 4; j++) { //所有小方块合法才改变。
if (!cells[status%statusNum][j].legal()) {
status--;
setColor(color);
return;
}
}
setColor(color);
}
7个实现类
都实现了父类的setcells方法,初始化各个状态的信息。以J方块举例,其完整代码如下:
public class J extends Tetromino{
public J(int x, int y) {
super(x,y);
statusNum=4;
color=2;
}
@Override
public void setCells(int x, int y) { //各个形状如下:
cells[0][0].setLocation(x-1, y-1); // *
cells[0][1].setLocation(x, y); // * * *
cells[0][2].setLocation(x, y -1);
cells[0][3].setLocation(x, y +1);
cells[1][0].setLocation(x-1, y); // * *
cells[1][1].setLocation(x, y); // *
cells[1][2].setLocation(x + 1, y); // *
cells[1][3].setLocation(x -1, y+1);
cells[2][0].setLocation(x+1, y+1); // * * *
cells[2][1].setLocation(x, y); // *
cells[2][2].setLocation(x , y-1);
cells[2][3].setLocation(x , y+1);
cells[3][0].setLocation(x-1, y); // *
cells[3][1].setLocation(x, y); // *
cells[3][2].setLocation(x +1, y); // * *
cells[3][3].setLocation(x +1, y-1);
}
}