今天,我們來使用Eclipse製作一個小遊戲《Tetris~》
資源在文末
一、遊戲機制
a) 7種小塊兒隨機生成
i. 一塊放置在頂部(隨時間下降)
ii. 另一塊置於右側,提示下一個隨機生成塊
iii. 七種形狀:“O、L、J、I、T、S、Z”
b) 牆與邊界
i. 小塊的移動範圍在邊框之內;
ii. 第一個小塊下降到最底部,觸底碰“牆”,自身嵌入“牆”;
iii. 下一小塊兒的其中某個小方格,底部碰到“牆”,自身嵌入“牆”;
c) 小塊兒的下降、左移、右移、旋轉與平移
i. 添加鍵盤監聽事件
ii. 不允許方塊的越界
iii. 確定旋轉中心,以旋轉中心的相對位置做其餘小塊的行列號修改
d) 消除與積分
i. 當某一排(col)被小方格佔滿,消除某一排
ii. 被消除排 的上方所有小方塊整體下移
iii. 積分累加
f) 遊戲的三種狀態:下落、暫停與重新開始
g) 遊戲結束的條件
i. 最上方的生成區域被佔,GameOver
二、設計需求 / 編程思想
【遵循javabean規範】
(1)添加兩個(至少)構造器【一個無參數,一個有參數】
(2)屬性一般爲私有化【外部類訪問不到】
(3)提供公有的get/set 【提供訪問途徑】
(4)toString用來描述對象信息
重寫toString屬性的信息
(5)重寫equals 【對象的值】
(6)重寫hashCode 【對象的哈希碼(地址)】
幾大部件:【我們要把這幾個傢伙抽象到類 (class) 中】
1.單個的小方格——class Cell
俄羅斯方塊中的最小單位:小方格
* 特徵:屬性
* row--行號
* col--列號
* image--對應圖片
*
* 行爲:
* 向左移動
* 向右移動
* 向下移動
對於image屬性,我們可以添加BufferImage類的對象image
記得導包(import)
2.每個不同形狀的四小塊兒組(7種形狀)——class X extends Tetromino
形狀有:O、L、J、I、T、S、Z
* Cell類繼承4塊整體(Trtromino)的屬性
* 構造器初始化
* Cell[] cells = new Cell[4];
* Cell(行號,列號,BufferedImage image)構造器
整體的四種狀態(旋轉),其中S、Z、I型可以只有兩種狀態,O只有一種狀態
*states = new State[4];
*
*/
3.四個小方格組成一個塊兒組——class Tetromino
四格方塊作爲一個整體
共同特徵:七種組合的父類
cell--四個方塊(Cell 數組存放)--protected Cell[ ] cells = new Cell[4]; //4個小方格創建,初值爲null
修飾詞protected【子類可訪問】
====================================================================
共同行爲:向左向右..
* 屬性:
* 4cells
* 行爲:
* 重寫toString
*
* moveLeft()
* moveRight()
* softDrop()軟下降——【按鍵盤'↓'只降一格】
=====================================================================
四種狀態:(相對與旋轉中心)
按照順時針順序:
4.主類——Tetris--------- extends JPanel
此類作爲程序的入口,加載靜態資源【圖片緩衝區】
擁有主方法main(){}
在主方法中添加:
1.JFrame(窗體框框)
2.JPanel(面板&畫筆)
(1).面板會自動調用繪製方法paint(Graphics g)
(2).重寫paint方法,繪製圖片背景
(3).繪製網格和嵌入牆中的方塊
3.在Panel中調用主邏輯start( )
====================================================================
把遊戲的主邏輯封裝入start( )方法中
1.KeyListener(開啓鍵盤監聽)
封裝匿名內部類 keyPressed(KeyEvent e){
【注意:k要小寫】
【Action後馬上重繪】
repaint();
};
2.面板添加監聽事件
3.當前對象設置成焦點
4.設置程序睡眠【每300ms生成新的小塊兒】
三、代碼實現
首先強調,Leo遵循的編寫順序爲:
(1).Cell類——小細胞
(2).Tetromino類——四個小細胞組成一個整體
(3).七種形狀類“O、L、J、I、T、S、Z”——繼承Tetromino的屬性和行爲
(4).最後是主類Tetris——作爲遊戲的主邏輯和程序的入口
1.Cell類
import java.awt.image.BufferedImage;
/*
* 俄羅斯方塊中的最小單位:小方格
* 特徵:屬性
* row--行號
* col--列號
* image--對應圖片
*
* 行爲:
* 向左
* 向右
* 向下
*/
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 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;
}
public void left(){
col --;
}
public void right(){
col ++;
}
public void drop(){
row ++;
}
@Override
public String toString() {
return "(" + row + ", " + col + ")" ;
}
}
2.Tetromino類
import java.util.Arrays;
/*
* 4格方塊作爲一個整體
* 屬性:
* 4cells
* 行爲:
* 重寫toString
*
* moveLeft()
* moveRight()
* softDrop()軟下降,按↓箭頭只下移一個單位
*/
public class Tetromino {
protected Cell[] cells = new Cell[4]; //4個null
//每個方塊都向左 一個單位移動
public void moveLeft(){
for (int i = 0; i < cells.length; i++) {
Cell cell = cells[i];
cell.left();
}
}
//每個方塊都向右 一個單位移動
public void moveRight(){
for (int i = 0; i < cells.length; i++) {
Cell cell = cells[i];
cell.right();
}
}
//每個方塊都向下 一個單位移動
public void softDrop(){
//強循環數組遍歷
for(Cell c:cells){
c.drop();
}
}
@Override
public String toString() {
return Arrays.toString(cells) ;
}
public static Tetromino randomOne(){
//隨機生成4格方塊
//四小塊作爲一整體,t賦爲null
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 L();break;
case 3:t=new J();break;
case 4:t=new I();break;
case 5:t=new Z();break;
case 6:t=new S();break;
}
return t;
}
public static Tetromino randomOne(){
//隨機生成4格方塊
//四小塊作爲一整體,t賦爲null
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 L();break;
case 3:t=new J();break;
case 4:t=new I();break;
case 5:t=new Z();break;
case 6:t=new S();break;
}
return t;
}
//旋轉四格方塊:順時針
public void rotateRight(){
//旋轉一次,計數器自增1
count++;
State s = state[count%state.length];
//需要獲取軸的行號和列號
Cell c = cells[0];
int row = c.getRow();
int col = c.getCol();
cells[1].setRow(row+s.row1);
cells[1].setCol(col+s.col1);
cells[2].setRow(row+s.row2);
cells[2].setCol(col+s.col2);
cells[3].setRow(row+s.row3);
cells[3].setCol(col+s.col3);
}
//旋轉四格方塊:逆時針
public void rotateLeft(){
//旋轉一次,計數器自減1
count--;
State s = state[count%state.length];
//需要獲取軸的行號和列號
Cell c = cells[0];
int row = c.getRow();
int col = c.getCol();
cells[1].setRow(row+s.row1);
cells[1].setCol(col+s.col1);
cells[2].setRow(row+s.row2);
cells[2].setCol(col+s.col2);
cells[3].setRow(row+s.row3);
cells[3].setCol(col+s.col3);
}
/*
* 定義內部類,用於封裝每次
* 旋轉後的相對於軸的其他三個小格子的行列號
*/
public class State{
/*
* 設置8個座標int
* 分別存儲四格方塊元素的相對位置
*/
int row0,col0;
int row1,col1;
int row2,col2;
int row3,col3;
public State() {}
public State(int row0, int col0, int row1, int col1, int row2, int col2, int row3, int col3) {
super();
this.row0 = row0;
this.col0 = col0;
this.row1 = row1;
this.col1 = col1;
this.row2 = row2;
this.col2 = col2;
this.row3 = row3;
this.col3 = col3;
}
}
3.七種形狀方塊兒 X 類——extends Tetromino
下面以“T”形狀爲例
public class T extends Tetromino{
/*
* 繼承4塊整體(Trtromino)的屬性
* 構造器初始化
* T型四格方塊的位置
* 000000
* 00
*/
public T(){
/*
* Cell[] cells = new Cell[4];
* Cell(行號,列號,BufferedImage image)構造器
*/
cells[0]= new Cell(0,4,Tetris.T); // 初始狀態旋轉中心的座標
cells[1]= new Cell(0,3,Tetris.T); // 初始狀態1號小細胞..
cells[2]= new Cell(0,5,Tetris.T); // 初始狀態2號...
cells[3]= new Cell(1,4,Tetris.T); // ...
state = new State[4]; // 【注意】,不要重新聲明State,在Tetromino.class中已聲明過了
//第一個狀態
state [0] = new State(0,0, 0,-1, 0,1, 1,0);
//第二個狀態
state [1] = new State(0,0, -1,0, 1,0, 0,-1);
state [2] = new State(0,0, 0,1, 0,-1, -1,0);
state [3] = new State(0,0, 1,0, -1,0, 0,1);
}
}
4.Tetris主類——extends JPanel(待續)
import java.awt.Graphics;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
/*
* 俄羅斯方塊的主類:
* 前提: 一塊麪板JPanel,可以嵌入窗口
* 面板上自帶一個畫筆,有一個功能,自動繪製。
* 調用了JPanel
* 加載靜態資源
*
*/
import java.awt.image.BufferedImage;
import java.util.Arrays;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Tetris extends JPanel{
/*
* 俄羅斯方塊的主類
* 功能: 1.加載靜態資源(背景圖,7種形狀的原始圖)
* 2.設置窗口
* 3.重寫JPanel中的paint()方法,構造器中添加畫筆Graphics g
* 前提:是一塊麪板Panel,可被嵌入窗口
* 屬性:1.正在下落的方塊
* 2.即將下落的方塊
* 3.添加牆(方格),畫舉矩形
*
*/
//屬性:正在下落的方塊
private Tetromino currentOne = Tetromino.randomOne();
//屬性:即將下落的方塊
private Tetromino nextOne = Tetromino.randomOne();
//屬性:牆 20行10列的方格
private Cell[][] wall= new Cell[20][10];
//屬性:統計分數
int [] scoresPool = {0,1,3,5,10};
private int totalScore = 0;
private int totalLine = 0;
//圖片加載:靜態
private static final int CELL_SIZE = 26;
public static BufferedImage 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{
/*
* getResource(String url)
* url:加載圖片的路徑
* 操作:將圖片文件拖入package Tetris_Day01
* 相對位置是同一個包下
*/
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();
}
}
/*
* 重寫JPanel中的paint()方法
*/
public void paint(Graphics g){
//繪製背景(圖片,橫座標,縱座標,observer)
/*
* g是畫筆
* g.drawImage(image,x,y,null)
* x,y爲開始繪製時的座標
*/
g.drawImage(background, 0, 0, null);
//平移座標軸
g.translate(15, 15);
//繪製牆
paintWall(g);
//繪製正在下落的4格方塊
paintCurrentOne(g);
//繪製下一個將要繪製的4格方塊
paintNextOne(g);
}
/*
* 繪製下一個將要繪製的4格方塊
* 繪製到右上角相應區域
*/
public void paintNextOne(Graphics g) {
//獲取Next對象的4個小方格
Cell[] cells = nextOne.cells;
for (Cell c : cells){
//獲取每一個元素的行列號
int row = c.getRow();
int col = c.getCol();
//橫座標
int x = col*CELL_SIZE+260;
int y = row*CELL_SIZE+26;
g.drawImage(c.getImage(), x, y, null);
}
}
public void paintCurrentOne(Graphics a){
//Cell類型的數組指向 4方格整體的對象currentOne的cells數組
Cell[] cells = currentOne.cells;
for (Cell c : cells) {
int x = c.getCol()*CELL_SIZE;
int y = c.getRow()*CELL_SIZE;
a.drawImage(c.getImage(), x, y, null);
}
}
public void paintWall(Graphics a){
//外層循環控制行數
//內層循環控制列數
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)
{
a.drawRect(x , y, CELL_SIZE, CELL_SIZE);
}else {
a.drawImage(cell.getImage(),x,y,null);
}
}
}
}
//所有的主邏輯
public void start(){
//開啓鍵盤監聽事件
KeyListener l = new KeyAdapter(){
//匿名內部類
@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;
case KeyEvent.VK_UP:
rotateRightAction();break;
case KeyEvent.VK_SPACE:
handDropAction();
}
//Action後馬上重繪
repaint();
}
};
//面板添加監聽事件
this.addKeyListener(l);
//當前對象設置成焦點
this.requestFocus();
while(true){
/*
* 當程序運行到此,進入睡眠狀態
* 睡眠時間爲300毫秒
*/
try {
Thread.sleep(900);
} catch (InterruptedException e) {
//打斷異常
e.printStackTrace();
}
//判斷可以下落
if(canDrop()){
currentOne.softDrop();
}else{
landToWall();
//將下一個下落的四格方塊賦值給CurrentOne
destroy();
currentOne = nextOne;
nextOne = Tetromino.randomOne();
}
/*
* 下落之後,重新進行繪製
* 纔會在看到下一步的動作
* repaint方法,也是JPanel類的方法
* 此方法調用paint
*/
repaint();
}
}
/*
*滿一行就消除,上方所有方塊向下移
*/
public void destroy(){
//統計銷燬行的次數
int lines = 0;
Cell[] cells = currentOne.cells;
for (Cell c : cells){
//取出每個元素的行號
int row = c.getRow();
while (row<20){
if(isFullLine(row)){
lines++;
wall[row] = new Cell[10];
for(int i = row;i>0;i--){
System.arraycopy(wall[i-1], 0, wall[i], 0, 10);
}
wall[0] = new Cell[10];
}
row++;
}
}
totalScore += scoresPool[lines];
}
//判斷行是否爲
public boolean isFullLine(int row) {
Cell[] line = wall[row];
for(Cell r : line){
if(r == null){
return false;
}
}
return true;
}
//順時針旋轉的動作
public void rotateRightAction() {
currentOne.rotateRight();
//先判斷越界,再判斷重合
if(outOfBounds()||coincide()){
currentOne.rotateLeft();
}
}
protected void moveRightAction() {
currentOne.moveRight();
//先判斷越界,再判斷重合
if(outOfBounds()||coincide()){
currentOne.moveLeft();
}
}
//監聽使用left鍵控制向左的行爲
protected void moveLeftAction() {
currentOne.moveLeft();
//先判斷越界,再判斷重合
if(outOfBounds()||coincide()){
currentOne.moveRight(); //越界,到-1,再移動回來
}
}
//判斷越界
public boolean outOfBounds(){
Cell[] cells = currentOne.cells;
for(Cell c : cells){
int col = c.getCol();
int row = c.getRow();
if(col<0 || col>9 || row>19 || row<0){
return true;
}
}
return false;
}
//判斷重合
public boolean coincide(){
Cell[] cells = currentOne.cells;
for(Cell c:cells){
int row = c.getRow();
int col = c.getCol();
if(wall[row][col]!=null){
return true;
}
}
return false;
}
public void softDropAction(){
if(canDrop()){
currentOne.softDrop();
}else{
//將下一個下落的四格方塊賦值給CurrentOne
landToWall();
destroy();
currentOne = nextOne;
nextOne = Tetromino.randomOne();
}
}
public void handDropAction(){
// while(canDrop()){
//
// }
for(;;){
if(canDrop()){
currentOne.softDrop();
}else{
break;
}
}
landToWall();
destroy();
currentOne = nextOne;
nextOne = Tetromino.randomOne();
}
public boolean canDrop(){
Cell[] cells = currentOne.cells;
/*
* 4格拿出來,遍歷
*/
for(Cell c:cells){
/*
* 獲取每個元素的行、列號
* 判斷:只要有一個元素的下一行有塊
* 或 有一個元素到最後一行
* 就不能下落了
*/
int row = c.getRow();
int col = c.getCol();
if(row == 19){
return false;
}
if(wall[row+1][col]!=null){
return false;
}
}
return true;
}
public void landToWall(){
Cell[] cells = currentOne.cells;
/*
* 4格拿出來,遍歷
*/
for(Cell c:cells){
int row = c.getRow();
int col = c.getCol();
wall[row][col] = c;
}
}
public static void main(String[] args) {
//1.創建窗口對象
JFrame frame = new JFrame("Tetris~");
frame.setVisible(true); //2.可見性
frame.setSize(535, 580); //3.size
frame.setLocationRelativeTo(null); //4.居中
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //5.關閉並終止遊戲
Tetris panel = new Tetris(); //6.創建遊戲界面,即面板
frame.add(panel); //7.將面板嵌入窗口
// panel.setBackground(Color.yellow); //8.先把面板畫成yellow
//其實調用的是JPanel中的paint()方法
//遊戲的主要邏輯封裝在start()
panel.start();
}
}
鏈接:https://pan.baidu.com/s/1wswwkTyDRdNGs_TUKYrlPw 密碼:t5tj
將資源解壓後拷貝至package下