俄罗斯方块小游戏--Java面向对象编程项目

前言:

写俄罗斯方块需要一些材料,以下为百度云链接,点击下载(若链接失效,请联系我)

俄罗斯方块需要的材料链接


正文:

    第一步:

        分析游戏界面(以下界面并不完成,只是一部分),通过游戏界面,抽象出几种类型


        从图中我们可以看出有一个表格(就是界限,我们称之为墙,在此例子中墙的大小为20行10列)和元胞(就是一个方格)。

        俄罗斯方块下落的图案有七种类型,还会显示下一个图案的类型。这七种类型类似于字母T、I、O、J、L、S、Z,一种类型对应一种颜色。(意思就是T类型的就用黄色的四个元胞表示,O类型用红色的四个元胞表示,以下图片在资源包中含有)

        然后每个类型都可以向左移动,向右移动和向下移动。

第二步:

    定义类型

  1. 元胞用Cell类表示,表示一个方格

             共同特征:行号、列号,图片

             共同行为:向左、向右、向下移动、提供JavaBean相关的规范

//Cell.java
package com.tetris;
/**
 * 俄罗斯方块中的最小单位:一个方格
 * 特征:
 *  row---行号
 *  col---列号
 *  image---对应的图片
 * 行为:
 *  向左移动---left()
 *  向右移动---right()
 *  下落---drop()
 * @author DELL
 *
 */

import java.awt.image.BufferedImage;

public class Cell {
	private int row;//行号
	private int col;//列号
	private BufferedImage image;//图片
	
	public Cell() {}
	public Cell(int row, int col, BufferedImage image) {
		super();
		this.row = row;
		this.col = col;
		this.image = image;
	}
	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() {
		// TODO Auto-generated method stub
		return "[row="+row+",col="+col+"]";
	}
	
	public void left() {
		col--;
	}
	
	public void right() {
		col++;
	}
	
	public void drop() {
		row++;
	}
}

     因为每种类型都是由四个元胞组成的,每种类型都可以向左、向右、向下移动,所以我们可以提取一个父类,我们取名为Tetromino

    2 . Tetromino类,是所有七种类型的父类

            共同特征:cells-->四个元胞(用数组表示)-->权限修饰词protected(方便之后七种类型继承时可以   获取)

            共同行为:向左、向右、向下移动、提供JavaBean相关的规范

           额外的方法:在俄罗斯方块中,我们会随机获得一种类型的图案,为此,我们需要写一个方法来随机获得一个类型。

//Tetromino.java

package com.tetris;


import java.util.Arrays;

/**
 * 四格方块
 *  属性:
 *   cells---四个方块
 *  行为:
 *   moveLeft()---左移动
 *   moveRight()---右移动
 *   softDrop()---软下落
 * @author DELL
 *
 */
public class Tetromino {
	protected Cell[] cells=new Cell[4];
	
	
	public Cell[] getCells() {
		return cells;
	}

	public void setCells(Cell[] cells) {
		this.cells = cells;
	}

	public void moveLeft() {
		for (int i = 0; i < cells.length; i++) {
			cells[i].left();
		}
	}
	
	public void moveRight() {
		for (int i = 0; i < cells.length; i++) {
			cells[i].right();
		}
	}
	
	public void softDrop() {
		for (int i = 0; i < cells.length; i++) {
			cells[i].drop();
		}
	}
	@Override
	public String toString() {
		// TODO Auto-generated method stub
		return "["+Arrays.toString(cells)+"]";
	}
	/*
	 * 随机生成一个四格方块
	 */
	public static Tetromino randomOne() {
		Tetromino t=null;
		int n=(int)(Math.random()*7);
		switch(n) {
		case 0:t=new T();break;//获取T类型,以下依次类推
		case 1:t=new I();break;
		case 2:t=new O();break;
		case 3:t=new S();break;
		case 4:t=new Z();break;
		case 5:t=new L();break;
		case 6:t=new J();break;
		}
		return t;
	}
}

    3 . T、I、O、L、J、S、Z类,是七种图案,继承Tetromino类

            因为此例子中我们是10行,所以初始化时我们行号和列号的复制如图所示(从左到右,从上到下,依次为I、S、T、Z、L、O、J型)

            (cells数组的存放需按照图上的顺序来,因为之后我们会旋转图形,需以cells[0]为轴心进行旋转)

            以T类为例子,其它六种类可以模仿依次写完

//T.java

package com.tetris;

public class T extends Tetromino {
	/*
	 * 提供构造器,进行初始化
	 * T型的四格方块的位置
	 */
	public T() {
		cells[0]=new Cell(0,4,Tetris.T);//Tetris.T是在主类中加载的静态资源,具体可以详见第三步中
		cells[1]=new Cell(0,3,Tetris.T);
		cells[2]=new Cell(0,5,Tetris.T);
		cells[3]=new Cell(1,4,Tetris.T);
	}
} 

第三步:

       在主类也就是Tetris.java中 加载静态资源,这里的静态资源有资源包中的七种类型的元胞图片和背景图片

//Tetris.java
//以下内容直接写在Tetris类中
        public static BufferedImage T;
	public static BufferedImage I;
	public static BufferedImage O;
	public static BufferedImage S;
	public static BufferedImage Z;
	public static BufferedImage L;
	public static BufferedImage J;
	public static BufferedImage background;
	
	
	static {
		/*
		 * getResource(String url)
		 * url:加载图片的路径
		 * 相对位置是同包下
		 */
		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"));
			S=ImageIO.read(Tetris.class.getResource("S.png"));
			Z=ImageIO.read(Tetris.class.getResource("Z.png"));
			L=ImageIO.read(Tetris.class.getResource("L.png"));
			J=ImageIO.read(Tetris.class.getResource("J.png"));
			background=ImageIO.read(Tetris.class.getResource("tetris.png"));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

第四步:

    在Tetris类中写main方法,在main方法中写一个窗口,并设置一些窗口的属性,并且让这个Tetris类继承JPanel,即这个主类是以一块面板,从而嵌入窗口Jframe。

//Tetris.java中main方法中的一部分内容

//别忘记让Tetris继承JPanel

                //创建窗口
		JFrame frame=new JFrame("俄罗斯方块");
		//设置窗口的可见性
		frame.setVisible(true);
		//设置窗口的大小
		frame.setSize(535, 580);
		frame.setLocationRelativeTo(null);//窗口居中
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//窗口关闭即程序结束
                //创建游戏界面,即面板
                Tetris panel=new Tetris();
                frame.add(panel);

第五步:

        面板上自带一个画笔,有一个功能:自动绘制--->JPanel里的paint()       

       因此我们重写JPanel中的paint方法,此方法中我们需要绘画出背景图片、绘制墙(也就是20x10的表格)、绘制正在下落的四个方块和下一个下落的方块。

       由此引出三个额外需要写的三个方法和若干个属性:

                方法:paintWall(Graphics)、paintCurrentOne(Graphics)、paintNextOne(Graphics)

                属性:正在下落的四格方块 :currentOne
                          下一个下落的四格方块 :nextOne
                          墙:20行10列的表格 :Cell[][] wall=new Cell[20][10]
                          每个元胞的宽度:private static final int CELL_SIZE=26

        还有一个问题就是我们的开始绘制时的原点在最左上角(向右x轴无穷大,向下y轴无穷大),考虑到背景图片的问题,所以我们需要将坐标轴平移15个像素。

//Tetris类中

        //属性:正在下落的四格方块
	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;

     /*
      * 重写JPanel中的paint(Graphics g)
      */
    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 paintNextOne(Graphics g) {
        //获取nextone的四格方块
        Cell[] cells=nextOne.cells;
        for (Cell cell : cells) {
            //获取每个元素的行号和列号
            int row=cell.getRow();
            int col=cell.getCol();
            //求出对应的横坐标和纵坐标
            int x=col*CELL_SIZE+260;
            int y=row*CELL_SIZE+26;
            //在面板中绘画出来
            g.drawImage(cell.getImage(), x, y, null);
        }
        
    }
    /*
     * 绘制正在下落的四格方块
     * 取出数组的元素
     * 绘制元素的图片
     * 横坐标x
     * 纵坐标y
     */
    public void paintCurrentOne(Graphics g) {
        Cell[] cells =currentOne.cells;
        for (Cell cell : cells) {
            int x=cell.getCol()*CELL_SIZE;
            int y=cell.getRow()*CELL_SIZE;
            g.drawImage(cell.getImage(), x, y, null);
        }
    }
    //绘制墙的函数:20行10列的表格,是个二维数组,使用双层循环。
    public void paintWall(Graphics g) {
        for (int i = 0; i < 20; i++) {
            for(int j=0;j<10;j++) {
                int x=CELL_SIZE*j;
                int y=CELL_SIZE*i;
                //如果墙中的位置为空,那么就画一个小格子,否则将图案画出来
                Cell cell=wall[i][j];
                if(cell==null) {
                    g.drawRect(x, y, CELL_SIZE, CELL_SIZE);
                }else{
                    g.drawImage(cell.getImage(), x, y, null);
                }
            }
        }
        
    }

第六步:

        基本上我们框架已经搭好,我们现在要做的就是让俄罗斯方块可以向左向右和向下动起来。我们在主类Tetris中定义一个start()方法,并且在main方法中进行调用

        start()方法将用来封装游戏的主要逻辑

                1 . 一个死循环,让currentOne每0.3秒往下下落一格,如果可以继续往下下落,则继续下落,否则就将currentOne嵌入到墙内并绘制出来,并且进行下一个图案的下落。

                2 . 添加键盘监听,当按下↓时进行下落,按下←时向左移,按下→时向右移。(new 一个键盘监听适配器,并且重写keyPressed方法)

        以上两个功能引出以下方法:

                判断是否可以下落:canDrop()

                将图案嵌入墙内:landToWall()

                执行下落步骤:softDrop()

                执行向左移动步骤:moveLeftAction()

                执行向右移动步骤:moveRightAction()

                判断是否越界(左右越界,即列号越界):outOfBounds()

                判断是否重合(在不越界的基础上进行判断):coincide()

//Tetris类中

        /*
         * 封装了游戏的主要逻辑
	 */
	public void start() {
		//开启键盘监听
		KeyListener keylistener=new KeyAdapter() {

			@Override
			public void keyPressed(KeyEvent arg0) {
				int code=arg0.getKeyCode();
				switch (code) {
				case KeyEvent.VK_DOWN:
					softDropAction();
					break;
				case KeyEvent.VK_LEFT:
					moveLeftAction();
					break;
				case KeyEvent.VK_RIGHT:
					moveRightAction();
					break;
				}
				repaint();
			}
			
		};
		
		//添加键盘监听
		this.addKeyListener(keylistener);
		//获得焦点
		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();
			
		}
	}
	/*
	 * 执行下落步骤
	 */
	protected void softDropAction() {
		if(canDrop()) {
			currentOne.softDrop();
		}else {
			landToWall();
			//将下一个下落的四个方块复制到正在下落的方格
			currentOne=nextOne;
			nextOne=Tetromino.randomOne();
		}//else这块是为了完美体验度
	}
	
	protected void moveLeftAction() {
		currentOne.moveLeft();
		if(outOfBounds()||coincide()) {
			currentOne.moveRight();
		}
	}
	
	protected void moveRightAction() {
		currentOne.moveRight();
		if(outOfBounds()||coincide()) {
			currentOne.moveLeft();
		}
	}
	//越界
	public boolean outOfBounds() {
		Cell[] cells=currentOne.cells;
		for (Cell cell : cells) {
			int col=cell.getCol();
			if(col<0||col>9) {
				return true;
			}
		}
		return false;
	}
	//重合
	public 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;
	}
	/*
	 * 判断是否可以下落
	 */
	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;
		}
	}


未完待续。。。。


猜你喜欢

转载自blog.csdn.net/qq_36590808/article/details/80464537
今日推荐