最近在老师带领下做了一个俄罗斯方块的小游戏~
最后运行界面是这样的:
小方格和背景的图片都是现成的,大致思路是:
代表每个小方格移动的Cell类->组成好的块(四格小方格组成的大块)的类Tetromino->表示每个具体图形的位置和颜色的类(一共七个)->进行游戏具体操作的类Tetris
Cell类比较简单,属性有行、列和属性,提供相应的get、set函数,以及每个小块的向左、向右、向下移动。
Tetromino类也同样,整合了Cell类,其中的方法实现四格方块整体的向左、向右、向下移动,以及随机生成一个正在下落的块和将要下落的块。
Tetris类就比较复杂啦..
首先是初始界面。
界面需要继承JPanel类,重写paint方法实现
在面板上自动绘制图形,JFrame与JPanel的区别我在网上查询到的是:
Jpanel不是顶级窗口,不能直接输出。它必须放在像JFrame这样的顶级窗口上才能输出,
JFrame只是一个界面,也就是个框架,要想把控件放在该界面中,必须把控件放在JPanel中,然后再把JPanel放在JFrame中,JPanel作为一个容器使用。
表示小方块和背景的图片需要放到和程序一个包下,设置为静态,每次初次加载程序即可加载完成,没有卡顿的可能性。
public static BufferedImage T; //T形状的方块 以下以此类推 public static BufferedImage I;
添加图片的过程也是静态的,加载一次即可。
static { try { T=ImageIO.read(Tetris.class.getResource("T.png")); //其他以此类推 background=ImageIO.read(Tetris.class.getResource("tetris.png")); }catch(Exception e) { e.printStackTrace(); } }
然后是绘制格子。
if(cell==null) { g.drawRect(x, y, CELL_SIZE, CELL_SIZE);} //x表示横坐标 else { g.drawImage(cell.getImage(),x,y,null);}
通过两层for循环实现绘制格子,在内层循环中如果wall[i][j]位置为空,则绘制一个空的格子,如果不空,则绘制该位置应有的格子图片,即为了在每个块落到底部时能被绘制出来。
然后是绘制正在和即将下落的方块。
需要调用在Tetromino类中写好的随机获取某个方块的函数(随机0-6,每个数字代表一个图形),获取后确定了图形的形状和初始位置,绘制出来即可。
接下来是图形的下落过程。
重写paint方法,在方法中绘制墙、正在下落和即将下落的方块。paint方法将被自动调用,不需要显式调用。
到现在玩游戏前的页面就做好了。
Tetris类的属性有正在下落、即将下落的四格方块组成的图形(有七种)和墙(方块在墙上移动,墙为20*10的方格)。
private Tetromino currentOne=Tetromino.randomOne(); private Tetromino nextOne=Tetromino.randomOne();
private Cell[][] wall=new Cell[20][10]; //设置墙为20*10的方格组合 private static final int CELL_SIZE=26; //宽度为26
接下来是玩游戏的方法,也是游戏的主要逻辑。
定义start方法,在其中创建一个键盘监听器对象,接受键盘的下左右事件,并重新绘制新位置的图形(repaint方法即可实现,
repaint()通过调用
线程
再由线程去调用update()方法清除当前显示并再调用paint()方法进行绘制下一个需要显示的内容.这样就起到了一种图片的交替显示从而在视角上形成了动画,update()即用来清除当前显示并调用paint()方法)。
为了使程序的运行速度看起来慢一些(即为了防止块下落的过快而人来不及反应),设置了程序的休眠时间为300毫秒。
也需要判断是否能继续下落,即下一行的格有没有方块或者有没有到达底部,如果没有则继续下落,如果有则停止下落,在墙上的相应位置设置有方块(即非null),并更新下一个图形。再调用repaint函数,使
键盘没有任何操作时,
图
形仍能继续下落。
在键盘有操作时,需要判断是按了哪个键,这件事键盘监听的对象即可做到。不同的键选择不同的操作。在判断没有左右出界和即将移动的地方没有其他方块后,调用Tetromino中的移动函数,移动整个图形,如果不再能改变位置,则在墙上表明位置然后绘制这个图形。(在此处需要在键盘产生时间后立刻改变墙的位置,在下次画图时能直接画到移动到的位置,如果按很多次下而不实时改变墙中的位置,则会影响游戏体验,方块并没有因为按动的频率加快而速度加快,不符合游戏人的需要)。
在移动时,先向该移动的方向移动,如果出界或重合,则再向反方向移动一次。判断左右出界时,如果列小于0或者大于9则出界,判断即将移动的位置有无方块判断墙的对应位置是否为空即可。判断这个时不能先判断有无重合,因为在这种操作中可能存在列号为-1时,判断重合函数会报错,如果把判断出界放在前面,即会捕捉这个错误,或语句也不会继续执行。
上述即是目前简单实现方块移动的过程,之后还会实现图形的旋转~
附全部代码:
Cell类:
import java.awt.image.BufferedImage; /** * 俄罗斯方块中的最小单位-方格 * 属性:row col image * 行为(方法):left() right() down() */ public class Cell { private int row;//行 private int col;//列 private BufferedImage image; public Cell(int row, int col, BufferedImage image) { super(); this.row = row; this.col = col; this.image = image; } public Cell() { super(); } public int getRow() { return row; } public void setRow(int row) { this.row = row; } public int getCol() { return col; } public void setCol(int col) { this.col = col; } public BufferedImage getImage() { return image; } public void setImage(BufferedImage image) { this.image = image; } @Override public String toString() { return "Cell [row=" + row + ", col=" + col + ", image=" + image + "]"; } /* 向左移动*/ public void left() { col--; } /* 向右移动*/ public void right() { col++; } /* 向下移动*/ public void drop() { row++; } }
Tetromino类:
/** * 四格方块 * 属性: * --cells ---四个方块 * 行为; * moveLeft() * moveRight() * softDrop() * */ public class Tetromino { protected Cell[] cells=new Cell[4]; public void moveLeft() { for(Cell c:cells) c.left(); } public void moveRight() { for(int i=0;i<4;i++) cells[i].right(); } public void softDrop() { for(int i=0;i<4;i++) cells[i].drop(); } /* * 随机生成一个四格方块 */ public static Tetromino randomOne() { Tetromino t=null; int num=(int)(Math.random()*7); switch(num) { case 0: t=new T();break; case 1: t=new O();break; case 2: t=new I();break; case 3: t=new J();break; case 4: t=new L();break; case 5: t=new S();break; case 6: t=new Z();break; } return t; } }
Tetris类:
import java.awt.Color; import java.awt.Graphics; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.image.BufferedImage; import javax.imageio.ImageIO; import javax.swing.JFrame; import javax.swing.JPanel; /* * 俄罗斯方块的主类 * 加载静态资源 * 前提:必须是面板JPanel,可以嵌入窗口 * 面板上自带一个画笔,有一个功能:自动绘制 * 其实是调用了JPanel里的paint() */ public class Tetris extends JPanel{ /* * 属性:正在下落的四格方块 * 将要下落的四格方块 */ private Tetromino currentOne=Tetromino.randomOne(); private Tetromino nextOne=Tetromino.randomOne(); /* * 属性:墙 20*10的方格 宽度为26 */ private Cell[][] wall=new Cell[20][10]; private static final int CELL_SIZE=26; public static BufferedImage T; //T形状的方块 以下以此类推 public static BufferedImage I; public static BufferedImage O; public static BufferedImage J; public static BufferedImage L; public static BufferedImage S; public static BufferedImage Z; public static BufferedImage background; //背景图片 static { try { T=ImageIO.read(Tetris.class.getResource("T.png")); I=ImageIO.read(Tetris.class.getResource("I.png")); O=ImageIO.read(Tetris.class.getResource("O.png")); J=ImageIO.read(Tetris.class.getResource("J.png")); L=ImageIO.read(Tetris.class.getResource("L.png")); S=ImageIO.read(Tetris.class.getResource("S.png")); Z=ImageIO.read(Tetris.class.getResource("Z.png")); background=ImageIO.read(Tetris.class.getResource("tetris.png")); }catch(Exception e) { e.printStackTrace(); } } public void paintWall(Graphics g) { //外层循环控制行数 for(int i=0;i<20;i++) { //内层循环控制列数 for(int j=0;j<10;j++) { int x=j*CELL_SIZE; int y=i*CELL_SIZE; Cell cell=wall[i][j]; if(cell==null) { g.drawRect(x, y, CELL_SIZE, CELL_SIZE); } else { g.drawImage(cell.getImage(),x,y,null); } } } } public void paintCurrentOne(Graphics g) { Cell[] cells=currentOne.cells; for(int i=0;i<cells.length;i++) { int x=cells[i].getCol()*CELL_SIZE; int y=cells[i].getRow()*CELL_SIZE; g.drawImage(cells[i].getImage(), x, y, null); } } //绘制即将下落的另一个 public void paintNextOne(Graphics g) { Cell[] cells=nextOne.cells; for(Cell cell:cells) { int col=cell.getCol(); int row=cell.getRow(); int x=col*CELL_SIZE+260; int y=row*CELL_SIZE+26; g.drawImage(cell.getImage(), x, y, null); } } //重写JPanel中的paint方法 public void paint(Graphics g) { /* * 背景 g:画笔 g.drawImage(image,x,y,null) * image要绘制的图片 * x y 为开始绘制的横纵坐标 * */ g.drawImage(background, 0, 0,null); //平移坐标轴 g.translate(15, 15); //绘制墙 paintWall(g); //绘制正在下落的四格方块 paintCurrentOne(g); //绘制即将下落的四格方块 paintNextOne(g); } /* *封装了游戏的主要逻辑 */ public void start() { //开启键盘监听事件 KeyListener listener=new KeyAdapter() { /* * keyPressed()是键盘按钮按下去所调用的方法 */ @Override public void keyPressed(KeyEvent e) { //获取一个毽子的代号 int code=e.getKeyCode(); switch(code) { case KeyEvent.VK_DOWN: softDropAction(); break; case KeyEvent.VK_LEFT: moveLeftAction(); break; case KeyEvent.VK_RIGHT: moveRightAction(); break; } repaint(); } }; //面板添加事件监听对象listener this.addKeyListener(listener); //面板对象设置成焦点 this.requestFocus(); while(true) { /* * 当程序运行到此,会进入休眠状态 * 睡眠时间为200毫秒,单位为毫秒 * 300毫秒之后,会自动执行后续代码 */ try { Thread.sleep(300); }catch(InterruptedException e) { e.printStackTrace(); } if(canDrop()) { currentOne.softDrop(); } else { LandToWall(); currentOne=nextOne; nextOne=Tetromino.randomOne(); } /* * 下落之后,需要重新绘制,才会看到下落后的位置 * repaint方法发也是JPanel类中 提供的 * 此方法调用paint方法 */ repaint(); } } /* * 使用down控制四格方块的下落 */ public void softDropAction() { if(canDrop()) { currentOne.softDrop(); } else { LandToWall(); currentOne=nextOne; nextOne=Tetromino.randomOne(); } } /* * 使用left键控制向左的行为 */ public void moveLeftAction() { //没出界或者没和左面的方块重合 currentOne.moveLeft();; if(outOfBounds()||coincide()) { currentOne.moveRight();; } } private boolean coincide() { Cell[] cells=currentOne.cells; for(Cell cell:cells) { int row=cell.getRow(); int col=cell.getCol(); if(wall[row][col]!=null) { return true; } } return false; } private boolean outOfBounds() { Cell[] cells=currentOne.cells; for(Cell cell:cells) { int col=cell.getCol(); if(col<0||col>9) return true; } return false; } public void moveRightAction() { //没出界或者右面的方块重合 currentOne.moveRight(); //不可以改变这两个函数的位置 因为coincide里的数组不允许参数为-1的时候 if(outOfBounds()||coincide()) { currentOne.moveLeft(); } } /* * 判断是否下落 */ public boolean canDrop() { Cell[] cells=currentOne.cells; for(Cell cell:cells) { /* * 获取每个元素的行号和列号 * 判断 * 只要有一个元素的下一行有方块 * 或者只要有一个元素到达最后一行 * 就不能再下落了 */ int row=cell.getRow(); int col=cell.getCol(); if(row==19) { return false; } if(wall[row+1][col]!=null) { return false; } } return true; } /* * 当不能再下落时,需要将四格方块,嵌入到墙中 * 也就是存储在二维数组中相应位置中 */ public void LandToWall() { Cell[] cells=currentOne.cells; for(Cell cell:cells) { int row=cell.getRow(); int col=cell.getCol(); wall[row][col]=cell; } } //启动游戏的入口 public static void main(String[] args) { //1、创建一个窗口对象 JFrame frame=new JFrame("俄罗斯方块"); //创建游戏界面即面板 Tetris panel=new Tetris(); //将面板嵌入窗口 frame.add(panel); //2、设置为可见 frame.setVisible(true); //3、设置窗口大小尺寸 frame.setSize(535, 600); //4、设置窗口居中 frame.setLocationRelativeTo(null); //5、设置窗口关闭,即程序终止 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); panel.setBackground(Color.yellow); panel.start(); } }
各个图形对应的位置: