最近在老师带领下做了一个俄罗斯方块的小游戏~
最后运行界面是这样的:
![](https://i-blog.csdnimg.cn/blog_migrate/490441edaea83c5e5f1e7d3dab982a78.png)
小方格和背景的图片都是现成的,大致思路是:
代表每个小方格移动的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();
}
}
各个图形对应的位置:
![](https://i-blog.csdnimg.cn/blog_migrate/890f49b87bbfd9663f7b4925ff80fa99.png)