【Swing】——俄罗斯方块
程序员文章站
2022-07-08 10:18:03
该俄罗斯方块版本是教主第二次写的,也算是一次重构,主要有三个目标:1. 一是“大道至简”,用最简单的逻辑实现俄罗斯方块的功能;2. 二是“逻辑与视图分离”,解开游戏运行的逻辑与Swing渲染的耦合;3. 三是记录Swing,记录一下Swing的简单使用方法。...
一、前言
该俄罗斯方块版本是教主第二次写的,也算是一次重构,主要有三个目标:
- 一是“大道至简”,用最简单的逻辑实现俄罗斯方块的功能;
- 二是“逻辑与视图分离”,解开游戏运行的逻辑与Swing渲染的耦合;
- 三是记录Swing,记录一下Swing的简单使用方法。
二、设计
我们发现大部分俄罗斯方块都是由10x20的小格子组成,然后有7种类型的方块,每种类型方块的对应1~4种可以由旋转而得到的形状。
于是我们可以设置一个10x20的布尔矩阵作为全局矩阵,然后以4x4的布尔矩阵来实现方块。方块的移动即是方块矩阵在全局矩阵上的偏移;方块的旋转即是单位矩阵的旋转。方块的创建可以采用原型模型,创建7种方块的原型,新方块都克隆自这7种原型方块。
建模
综上,俄罗斯方块游戏需要的类有:
- 布尔矩阵(
BoolMatrix
):将二维数组封装为矩阵,以方便其它类的访问。 - 方块(
Tetris
) - 抽象游戏逻辑接口(
GameHandler
):定义数据获取和事件处理的接口,目的是为了以接口的方式解开逻辑与渲染的耦合。 - 具体游戏逻辑(
Game
):继承自GameHandler
,实现具体的游戏逻辑。 - 游戏主窗口(
GameView
):继承自JFrame
,组织各种组件以及负责事件的监听。 - 面板
GamePanel
:继承自JPanel
,负责渲染界面。 - 入口类
App
预览
疑问
1、为什么要写这么多类?
可以发现,俄罗斯方块本身的逻辑其实也就哪些,而这些逻辑好像又的确与视图的渲染没什么直接的关系。
也就是说,只要定义好俄罗斯方块游戏逻辑的访问接口(包括但不限于事件的处理、数据的读取),任何按照一定约定来访问该接口的渲染组件,都能够产生正确的交互并渲染出正确的界面。
2、为什么抽象游戏逻辑接口(GameHandler
)中的全局矩阵的宽高为12x22?
大道至简。
如果设置为10x20个小格子,那么除了碰撞问题还要处理移动越界的问题。而如果加一层值为1的边框,那么就可以统一的用碰撞问题去处理。
3、统一用一种方法判断会不会降低效率?
会。
但即便这样设计会增加不必要的循环和判断,视图约定以0.5秒的间隔去刷新和访问,对于逻辑运算和界面渲染来说也已经绰绰有余。另外Swing中的监听是单独的线程,但是触发的事件会被放入队列中,不存在多线程访问的同步问题。
4、一直创建方块会不会增加内存开销?
会。
可以使用享元模式来管理方块。将创建的方块放入享元工厂中就不用一直创建方块了。
三、代码
布尔矩阵
- 为了支持方块的原型模式,布尔矩阵需要实现
Cloneable
接口并重写Clone()
方法,对内置的二维数组进行深克隆。 - 并且方块都是4阶单位矩阵,通过对单位矩阵进行旋转操作来实现方块的形状变化。
- 对于方块的碰撞和越界,将全局矩阵加一层值为1的格子就可以统一用碰撞问题来处理,具体到代码就是判断方块矩阵经偏移后是否与全局矩阵有重合点(其中偏移量对应方块的x、y坐标)。
- 对于方块更替时的处理,可以先将方块矩阵经偏移后或运算到全局矩阵中,然后再将当前方块的引用指向下一个方块。
public class BoolMatrix implements Cloneable {
// 内置二维数组
private byte[][] array;
// 矩阵的行数
private int rows;
// 矩阵的列数
private int columns;
public BoolMatrix(int rows, int columns) {
this.rows = rows;
this.columns = columns;
this.array = new byte[rows][columns];
}
/**
* 对于传入的二维数组将会进行复制操作以使得矩阵不依赖实参
* @param array 二维数组
*/
public BoolMatrix(byte[][] array) {
this.rows = array.length;
this.columns = array[0].length;
this.array = new byte[this.rows][this.columns];
for (int i = 0; i < this.rows; i++) {
System.arraycopy(array[i], 0, this.array[i], 0, this.columns);
}
}
/**
* 判断源矩阵经偏移后是否与目标矩阵重合
* @param source 源矩阵
* @param target 目标矩阵
* @param xOffset 源矩阵在目标矩阵上的水平偏移量
* @param yOffset 源矩阵在目标矩阵上的垂直偏移量
* @return 源矩阵经偏移后是否与目标矩阵重合
*/
public static boolean overlapped(BoolMatrix source, BoolMatrix target, int xOffset, int yOffset) {
for (int i = 0; i < source.rows; i++) {
for (int j = 0; j < source.columns; j++) {
if (source.get(i, j) == 1 && target.get(i + yOffset, j + xOffset) == 1) {
return true;
}
}
}
return false;
}
/**
* 将源矩阵经偏移后或运算到目标矩阵上
* 修改的是目标矩阵
* @param source 源矩阵
* @param target 目标矩阵
* @param xOffset 源矩阵在目标矩阵上的水平偏移量
* @param yOffset 源矩阵在目标矩阵上的垂直偏移量
*/
public static void or(BoolMatrix source, BoolMatrix target, int xOffset, int yOffset) {
for (int i = 0; i < source.rows; i++) {
for (int j = 0; j < source.columns; j++) {
if (source.get(i, j) == 1) {
target.set(i + yOffset, j + xOffset, (byte) 1);
}
}
}
}
public byte get(int i, int j) {
return this.array[i][j];
}
public void set(int i, int j, byte value) {
this.array[i][j] = value;
}
/**
* 矩阵右旋
* @throws Exception 行列不相等的矩阵不支持旋转
*/
public void rotateRight() throws Exception {
if (this.rows != this.columns) {
throw new Exception("Rotate not supported");
} else {
int len = this.rows;
for (int i = 0; i < len; i++) {
for (int j = 0; j < len - i; j++) {
byte temp = this.array[i][j];
this.array[i][j] = this.array[len - j - 1][len - i - 1];
this.array[len - j - 1][len - i - 1] = temp;
}
}
for (int i = 0; i < len / 2; i++) {
for (int j = 0; j < len; j++) {
byte temp = this.array[i][j];
this.array[i][j] = this.array[len - i - 1][j];
this.array[len - i - 1][j] = temp;
}
}
}
}
/**
* 矩阵左旋
* @throws Exception 行列不相等的矩阵不支持旋转
*/
public void rotateLeft() throws Exception {
if (this.rows != this.columns) {
throw new Exception("Rotate not supported");
} else {
int len = this.rows;
for (int i = 0; i < len; i++) {
for (int j = i; j < len; j++) {
byte temp = this.array[i][j];
this.array[i][j] = this.array[j][i];
this.array[j][i] = temp;
}
}
for (int i = 0; i < len; i++) {
for (int j = 0; j < len / 2; j++) {
byte temp = this.array[i][j];
this.array[i][j] = this.array[i][len - j - 1];
this.array[i][len - j - 1] = temp;
}
}
}
}
@Override
public Object clone() throws CloneNotSupportedException {
BoolMatrix bsm = (BoolMatrix) super.clone();
bsm.array = bsm.array.clone();
for (int i = 0; i < bsm.array.length; i++) {
bsm.array[i] = bsm.array[i].clone();
}
return bsm;
}
public byte[][] getArray() {
return array;
}
public int getRows() {
return rows;
}
public int getColumns() {
return columns;
}
}
方块
- 将字段定义为
package-private
包访问权限可减少setter
方法。 - 为了支持原型模式,方块需要:
- 实现
Cloneable
接口并重写clone()
方法,对方块进行深克隆。 - 创建7种方块原型
- 私有化构造方法,以静态工厂的形式提供创建方块的途径,随机创建克隆自7种原型方块的新放方块,并初始化其坐标值。
- 实现
import java.awt.*;
import java.util.Random;
public class Tetris implements Cloneable {
// 7种原型单例
private static final Tetris RED_PROTOTYPE
= new Tetris(Color.RED, new BoolMatrix(new byte[][]{{0, 1, 0, 0},
{0, 1, 0, 0},
{0, 1, 0, 0},
{0, 1, 0, 0}}));
private static final Tetris ORANGE_PROTOTYPE
= new Tetris(Color.ORANGE, new BoolMatrix(new byte[][]{{0, 0, 0, 0},
{0, 1, 1, 0},
{0, 1, 1, 0},
{0, 0, 0, 0}}));
private static final Tetris YELLOW_PROTOTYPE
= new Tetris(Color.YELLOW, new BoolMatrix(new byte[][]{{0, 0, 0, 0},
{0, 1, 0, 0},
{1, 1, 1, 0},
{0, 0, 0, 0}}));
private static final Tetris GREEN_PROTOTYPE
= new Tetris(Color.GREEN, new BoolMatrix(new byte[][]{{0, 0, 1, 0},
{0, 0, 1, 0},
{0, 1, 1, 0},
{0, 0, 0, 0}}));
private static final Tetris CYAN_PROTOTYPE
= new Tetris(Color.CYAN, new BoolMatrix(new byte[][]{{0, 1, 0, 0},
{0, 1, 0, 0},
{0, 1, 1, 0},
{0, 0, 0, 0}}));
private static final Tetris BLUE_PROTOTYPE
= new Tetris(Color.BLUE, new BoolMatrix(new byte[][]{{0, 0, 1, 0},
{0, 1, 1, 0},
{0, 1, 0, 0},
{0, 0, 0, 0}}));
private static final Tetris MAGENTA_PROTOTYPE
= new Tetris(Color.MAGENTA, new BoolMatrix(new byte[][]{{0, 1, 0, 0},
{0, 1, 1, 0},
{0, 0, 1, 0},
{0, 0, 0, 0}}));
// 原型的颜色
Color prototype;
// 矩阵
BoolMatrix matrix;
// 当前的坐标/在全局矩阵的偏移量
int x;
int y;
/**
* 私有化构造方法,通过简单原型工厂方法提供创建新方块的途径
* @param prototype 原型的颜色
* @param matrix 矩阵
*/
private Tetris(Color prototype, BoolMatrix matrix) {
this.prototype = prototype;
this.matrix = matrix;
}
/**
* 根据随机原型深克隆创建方块
* @param x 新方块的x坐标/在全局矩阵的水平偏移量
* @param y 新方块的y坐标/在全局矩阵的垂直偏移量
* @return 随机新方块
*/
public static Tetris createTetris(int x, int y) {
Tetris t = null;
try {
int prototypeId = new Random().nextInt(7);
switch (prototypeId) {
case 0:
t = (Tetris) RED_PROTOTYPE.clone();
break;
case 1:
t = (Tetris) ORANGE_PROTOTYPE.clone();
break;
case 2:
t = (Tetris) YELLOW_PROTOTYPE.clone();
break;
case 3:
t = (Tetris) GREEN_PROTOTYPE.clone();
break;
case 4:
t = (Tetris) CYAN_PROTOTYPE.clone();
break;
case 5:
t = (Tetris) BLUE_PROTOTYPE.clone();
break;
case 6:
t = (Tetris) MAGENTA_PROTOTYPE.clone();
break;
default:
t = null;
}
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
// switch必定能克隆出方块
assert t != null;
t.x = x;
t.y = y;
return t;
}
public Color getPrototype() {
return prototype;
}
public BoolMatrix getMatrix() {
return matrix;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < this.matrix.getRows(); i++) {
for (int j = 0; j < this.matrix.getColumns(); j++) {
if (this.matrix.get(i, j) == 1) {
sb.append("◼");
} else {
sb.append("◻");
}
}
sb.append("\n");
}
return String.valueOf(sb);
}
@Override
public Object clone() throws CloneNotSupportedException {
Tetris t = (Tetris) super.clone();
t.matrix = (BoolMatrix) t.matrix.clone();
return t;
}
}
抽象游戏逻辑接口
public interface GameHandler {
/**
* 全局矩阵的宽高
*/
int GLOBAL_WIDTH = 10 + 2;
int GLOBAL_HEIGHT = 20 + 2;
/**
* 处理向左移动事件
*/
void handleLeftAction();
/**
* 处理向右移动事件
*/
void handleRightAction();
/**
* 处理旋转事件
*/
void handleRotateAction();
/**
* 处理速降事件
*/
void handleLandAction();
/**
* 请求调整(包括下降、清行、更新方块)
* @return 游戏是否尚未输掉
*/
boolean requestAdjust();
/**
* @return 全局矩阵
*/
BoolMatrix requestGlobal();
/**
* @return 当前方块
*/
Tetris requestCurrent();
/**
* @return 预览方块
*/
Tetris requestNext();
/**
* @return 分数/消行数
*/
int requestScore();
}
具体游戏逻辑
public class Game implements GameHandler {
// 全局矩阵
private BoolMatrix globalMatrix;
// 当前方块
private Tetris current;
// 预览方块
private Tetris next;
// 分数/消行数
private int score;
public Game() {
this.globalMatrix = new BoolMatrix(GLOBAL_HEIGHT, GLOBAL_WIDTH);
// 边框为1
for (int i = 0; i < GLOBAL_HEIGHT; i++) {
for (int j = 0; j < GLOBAL_WIDTH; j++) {
if ((i == 0 || i == GLOBAL_HEIGHT - 1)
|| (j == 0 || j == GLOBAL_WIDTH - 1)) {
this.globalMatrix.set(i, j, (byte) 1);
}
}
}
// 初始化方块
this.current = Tetris.createTetris(4, 1);
this.next = Tetris.createTetris(4, 1);
}
@Override
public void handleLeftAction() {
int preX = this.current.x - 1;
if (!BoolMatrix.overlapped(this.current.matrix,
this.globalMatrix,
preX,
this.current.y)) {
this.current.x = preX;
}
}
@Override
public void handleRightAction() {
int preX = this.current.x + 1;
if (!BoolMatrix.overlapped(this.current.matrix,
this.globalMatrix,
preX,
this.current.y)) {
this.current.x = preX;
}
}
@Override
public void handleRotateAction() {
try {
// 预先右转
this.current.matrix.rotateRight();
if (BoolMatrix.overlapped(this.current.matrix,
this.globalMatrix,
this.current.x,
this.current.y)) {
// 重合则撤销
this.current.matrix.rotateLeft();
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void handleLandAction() {
// 最长的距离为"一"型的方块将要重合的距离
int maxDistance = GLOBAL_HEIGHT - this.current.y - 1;
for (int i = 0; i <= maxDistance; i++) {
int preY = this.current.y + i;
if (BoolMatrix.overlapped(this.current.matrix,
this.globalMatrix,
this.current.x,
preY + 1)) {
this.current.y = preY;
break;
}
}
}
@Override
public boolean requestAdjust() {
// 判断输赢
for (int i = 1; i < GLOBAL_WIDTH - 1; i++) {
if (this.globalMatrix.get(3, i) == 1) {
return false;
}
}
int preY = this.current.y + 1;
if (!BoolMatrix.overlapped(this.current.matrix,
this.globalMatrix,
this.current.x,
preY)) {
// 能继续下降
this.current.y = preY;
} else {
// 不能继续下降
// 1.或运算到全局矩阵
BoolMatrix.or(this.current.matrix,
this.globalMatrix,
this.current.x,
this.current.y);
// 2.更新方块
this.current = this.next;
this.next = Tetris.createTetris(4, 1);
// 3.清空满行
int zeroRowIndex = -1;
for (int i = GLOBAL_HEIGHT - 2; i >= 1; i--) {
int num = 0;
for (int j = 1; j < GLOBAL_WIDTH - 1; j++) {
if (this.globalMatrix.get(i, j) == 1) {
num++;
}
}
if (num == GLOBAL_WIDTH - 2) {
for (int j = 1; j < GLOBAL_WIDTH - 1; j++) {
this.globalMatrix.set(i, j, (byte) 0);
}
// 每次只清空给一行
this.score++;
zeroRowIndex = i;
break;
}
}
// 4.向下填充
for (int i = zeroRowIndex + 1; i >= 2; i--) {
for (int j = 1; j < GLOBAL_WIDTH - 1; j++) {
if (this.globalMatrix.get(i, j) == 0 && this.globalMatrix.get(i - 1, j) == 1) {
this.globalMatrix.set(i, j, (byte) 1);
this.globalMatrix.set(i - 1, j, (byte) 0);
}
}
}
}
return true;
}
@Override
public BoolMatrix requestGlobal() {
return this.globalMatrix;
}
@Override
public Tetris requestCurrent() {
return this.current;
}
@Override
public Tetris requestNext() {
return this.next;
}
@Override
public int requestScore() {
return this.score;
}
}
主窗口
- 为了让代码块的概念更加清晰,可以将窗体的创建拆分为三个部分,分别是
buildBase()
、buildComponent()
、buildListener()
。 - 提供一个
start()
方法来开始运行游戏。其中在while
循环种间歇性的向抽象游戏罗杰接口发起调整请求,并让自己的面板组件重新绘制。当收到游戏输掉的消息后退出循环并弹出JDilalog
对话框来结束游戏。
注意事项
- 事件的监听需要获得焦点才能触发,可以在调用
setVisible()
方法前加上requestFocus()
,或者在其后加setFocusable(true)
,以使得组件能够获得焦点。 - 窗口运行在main线程中并且会阻塞主线程,而事件的监听时运行在独立的线程中,可以调用
Thread.currentThread.getName()
方法来获取当前线程的名称。
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class GameView extends JFrame {
// 调整后的窗口最佳宽高
private static final int FRAME_WIDTH = 560;
private static final int FRAME_HEIGHT = 720;
// 游戏处理者
private GameHandler handler;
// 绘制面板
private JPanel gamePanel;
// 提示栏
private JTextArea tipText;
// 游戏是否暂停
private boolean paused;
public GameView(GameHandler handler) {
this.handler = handler;
this.paused = false;
this.buildBase();
this.buildComponent();
this.buildListener();
// 设置窗口可见
this.setVisible(true);
// 设置窗口可获取焦点以触发事件监听
this.setFocusable(true);
}
/**
* 开始游戏
* 在while循环中以0.5秒的间隔阻塞绘制和调整请求
*/
public void start() {
boolean going = true;
while (going) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (!this.paused) {
going = handler.requestAdjust();
this.gamePanel.repaint();
}
}
// 游戏输掉后以弹出对话框的形式退出游戏并释放资源
this.setEnabled(false);
JDialog result = new JDialog(this, "GAME OVER");
result.setSize(this.getWidth() / 4, this.getHeight() / 8);
result.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
super.windowClosing(e);
GameView.this.dispose();
}
});
result.setLocationRelativeTo(null);
result.setResizable(false);
result.setVisible(true);
}
/**
* 添加窗口基本属性
*/
private void buildBase() {
this.setTitle("教主的俄罗斯方块");
this.setSize(FRAME_WIDTH, FRAME_HEIGHT);
this.setLocationRelativeTo(null);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setResizable(false);
}
/**
* 添加窗口组件
*/
private void buildComponent() {
this.gamePanel = new GamePanel(this.handler);
this.getContentPane().add(this.gamePanel, BorderLayout.CENTER);
this.tipText = new JTextArea();
this.tipText.setFont(new Font("微软雅黑", Font.PLAIN, 14));
this.tipText.setText("W : 旋转\t" + "A : 向左\t" + "S : 速降\t" + "D : 向右\t" + "Enter : 开始/暂停");
this.tipText.setBackground(null);
this.tipText.setEditable(false);
this.getContentPane().add(this.tipText, BorderLayout.SOUTH);
}
/**
* 添加事件监听
*/
private void buildListener() {
this.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
super.keyPressed(e);
switch (e.getKeyCode()) {
case KeyEvent.VK_W:
if (!GameView.this.paused) {
GameView.this.handler.handleRotateAction();
}
break;
case KeyEvent.VK_A:
if (!GameView.this.paused) {
GameView.this.handler.handleLeftAction();
}
break;
case KeyEvent.VK_S:
if (!GameView.this.paused) {
GameView.this.handler.handleLandAction();
}
break;
case KeyEvent.VK_D:
if (!GameView.this.paused) {
GameView.this.handler.handleRightAction();
}
break;
case KeyEvent.VK_ENTER:
GameView.this.paused = !GameView.this.paused;
break;
}
}
});
}
}
面板
可以引入560x720的背景图片于面板类的同级目录下,去掉注释掉的代码并修改图片名,运行时即可渲染背景图片。
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.io.IOException;
public class GamePanel extends JPanel {
// 矩阵单元格的长度
private static final int CELL_LEN = 30;
// 背景图
private static Image img;
/*
static {
try {
img = ImageIO.read(GamePanel.class.getResource("cartoon1.jpg"));
} catch (IOException e) {
e.printStackTrace();
}
}
*/
private GameHandler handler;
public GamePanel(GameHandler handler) {
this.handler = handler;
}
@Override
public void paint(Graphics g) {
super.paint(g);
this.paintImage(g);
this.paintGlobalMatrix(g);
this.paintCurrentMatrix(g);
this.paintNextMatrix(g);
this.paintScore(g);
}
private void paintImage(Graphics g) {
// g.drawImage(img, 0, 0, null);
}
private void paintGlobalMatrix(Graphics g) {
BoolMatrix globalMatrix = this.handler.requestGlobal();
for (int i = 0; i < GameHandler.GLOBAL_HEIGHT; i++) {
for (int j = 0; j < GameHandler.GLOBAL_WIDTH; j++) {
g.setColor(Color.BLACK);
g.drawRect(j * CELL_LEN, i * CELL_LEN, CELL_LEN, CELL_LEN);
if (globalMatrix.get(i, j) == 1) {
if (i == 0 || i == GameHandler.GLOBAL_HEIGHT - 1
|| j == 0 || j == GameHandler.GLOBAL_WIDTH - 1) {
// 边框值
g.setColor(Color.LIGHT_GRAY);
} else {
// 由其它方块复制过来的值
g.setColor(Color.GRAY);
}
// 不完全填充会好看一点儿
g.fillRect(j * CELL_LEN + 2,
i * CELL_LEN + 2,
CELL_LEN - 3,
CELL_LEN - 3);
}
}
}
}
private void paintCurrentMatrix(Graphics g) {
Tetris current = this.handler.requestCurrent();
BoolMatrix currentMatrix = current.getMatrix();
g.setColor(current.getPrototype());
for (int i = 0; i < currentMatrix.getRows(); i++) {
for (int j = 0; j < currentMatrix.getColumns(); j++) {
if (currentMatrix.get(i, j) == 1) {
g.fillRect((current.getX() + j) * CELL_LEN + 2,
(current.getY() + i) * CELL_LEN + 2,
CELL_LEN - 3,
CELL_LEN - 3);
}
}
}
}
private void paintNextMatrix(Graphics g) {
Tetris next = handler.requestNext();
BoolMatrix nextMatrix = next.getMatrix();
int positionX = (GameHandler.GLOBAL_WIDTH + 1) * CELL_LEN;
int positionY = CELL_LEN;
for (int i = 0; i < nextMatrix.getRows(); i++) {
for (int j = 0; j < nextMatrix.getColumns(); j++) {
g.setColor(Color.BLACK);
g.drawRect(positionX + j * CELL_LEN,
positionY + i * CELL_LEN,
CELL_LEN,
CELL_LEN);
if (nextMatrix.get(i, j) == 1) {
g.setColor(next.getPrototype());
g.fillRect(positionX + j * CELL_LEN + 2,
positionY + i * CELL_LEN + 2,
CELL_LEN - 3,
CELL_LEN - 3);
}
}
}
}
private void paintScore(Graphics g) {
int score = this.handler.requestScore();
int positionX = (GameHandler.GLOBAL_WIDTH + 1) * CELL_LEN;
int positionY = 10 * CELL_LEN;
g.setColor(Color.RED);
g.setFont(new Font("微软雅黑", Font.BOLD, CELL_LEN));
g.drawString("score = " + score, positionX, positionY);
}
}
入口
public class App {
public static void main(String[] args) {
GameHandler handler = new Game();
GameView view = new GameView(handler);
view.start();
}
}
四、Swing结构
- 其中窗体(
JFrame
)和对话框(JDialog
)都是作为顶层容器而存在的,通常不能直接向里面添加组件。顶层容器的显示需要调用setVisible()
方法并传参true
。 - 要想向顶层容器中添加组件通常需要借助中间容器,比如面板(
JPanel
)。还可以通过调用getContentPane()
方法来获得默认的中间容器。 - 窗体(
JFrame
)默认是边界布局(BorderLaylout
);面板(JPanel
)默认是流式布局(FlowLayout
)。
五、参考资料
本文地址:https://blog.csdn.net/XY1790026787/article/details/85943218
推荐阅读
-
【java】本地客户端内嵌浏览器3 - Swing 使用 Spring 框架 + 打包项目 + 转exe + 源码
-
Jtable删除多行 博客分类: swing
-
CS结构软件自动升级实现(四) 博客分类: swing GoXML.net
-
swing模仿web锚点效果 博客分类: swing SwingWeb
-
Html5原创俄罗斯方块(基于canvas)
-
Java Swing-10 Swing实例08Jlist
-
Swing学习10:Java Swing JComboBox
-
Java swing 带界面和进度条的多线程下载器实现
-
Java Swing创建自定义闪屏:在闪屏上画进度条(一)
-
Java Swing:进度条字体颜色修改