俄罗斯方块的小实现

最近在老师带领下做了一个俄罗斯方块的小游戏~
最后运行界面是这样的:
小方格和背景的图片都是现成的,大致思路是:

代表每个小方格移动的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();
		
	}
	
}

各个图形对应的位置:

猜你喜欢

转载自blog.csdn.net/weixin_40373090/article/details/80465562
今日推荐