首先这是拼图游戏的界面,我们最后要完成这样的界面。
我们要编写三个界面:登录界面,注册界面,游戏界面。
设置游戏界面:
import javax.swing.*;
import java.awt.*;
public class GameJFrame extends JFrame {
public GameJFrame() {
//设置界面的宽高
this.setSize(603, 680);
//设置界面标题
this.setTitle("拼图游戏");
//设置界面置顶
this.setAlwaysOnTop(true);
//设置界面居中
this.setLocationRelativeTo(null);
//设置关闭模式
this.setDefaultCloseOperation(3);
//显示界面
this.setVisible(true);
}
}
this.setDefaultCloseOperation(3);关闭模式为什么是3:
0:代表点击X的时候,什么都不做。
1:代表默认,什么都不写,代表的就是1。
2:代表如果有多个界面,只有关掉所有界面,程序才会停止。
3:代表只要关掉一个,程序就会停止。
接着创建菜单面板:
先创建JMenuBar:菜单
再创建Jmenu:功能
再创建JMenuItem:选项
再把JMenuItem放到Jmenu里面。
把JMenu放到JMenuBar里面
最后再把JMenuBar添加到整个Jframe界面中。
这里我们将初始化菜单和初始化界面分别放在两个方法里面。
private void initJMenuBar() {
//创建整个的菜单对象
JMenuBar jMenuBar=new JMenuBar();
//创建菜单上面的两个选项的对象(功能 关于我们)
JMenu functionJMenu=new JMenu("功能");
JMenu aboutJMenu=new JMenu("关于我们");
//创建选项下面的条目对象
JMenuItem replayItem =new JMenuItem("重新游戏");
JMenuItem reLoginItem =new JMenuItem("重新登录");
JMenuItem closeItem =new JMenuItem("关闭游戏");
JMenuItem accountItem=new JMenuItem("公众号");
//将每一个选项下面的条目添加到选项当中
functionJMenu.add(replayItem);
functionJMenu.add(reLoginItem);
functionJMenu.add(closeItem);
aboutJMenu.add(accountItem);
//将菜单里面的两个选项添加到菜单当中
jMenuBar.add(functionJMenu);
jMenuBar.add(aboutJMenu);
//给整个界面设置菜单
this.setJMenuBar(jMenuBar);
}
private void initJFrame() {
//设置界面的宽高
this.setSize(603, 680);
//设置界面标题
this.setTitle("拼图游戏");
//设置界面置顶
this.setAlwaysOnTop(true);
//设置界面居中
this.setLocationRelativeTo(null);
//设置关闭模式
this.setDefaultCloseOperation(3);
}
初始化图片:
先创建一个initImage方法,利用idea的功能创建一个方法,然后我们把图片放入到项目当中
我们在复制图片路径,我们使用ImageIcon方法。ImageIcon 是 Java 中 javax.swing 包下的一个类,它提供了一种简单的方式来处理和显示图像。这个类可以将图像加载到内存中,并将其转换为适合在 Swing 组件(如 JLabel)上显示的形式。
ImageIcon 类提供了多个构造方法,用于从不同的数据源创建图像图标,常见的构造方法如下:
ImageIcon(String filename):从指定的文件路径创建图像图标。
private void initImage() {
//创建一个图片ImageIcon的对象
ImageIcon icon=new ImageIcon("C:\\Users\\小新\\IdeaProjects\\Test\\dog\\5.png");
//创建一个JLabl的对象(管理容器)
JLabel label=new JLabel(icon);
this.add(label);
}
在 Java 的 Swing 体系里,像 JFrame 这样的顶层容器实际上是一个复合结构,它由多个层次组成,包括根面板(RootPane)、玻璃面板(GlassPane)、内容面板(ContentPane)等。其中,内容面板是我们实际用来添加子组件(如按钮、文本框、标签等)的区域。
getContentPane() 方法的主要意义在于获取顶层容器的内容面板,我们可以通过这个方法得到内容面板的引用,然后在这个内容面板上添加、移除或管理各种组件,从而实现界面的布局和设计。
label.setBounds(0,0,105,105);
this.getContentPane().add(label);
我们添加的图片现在默认是在正中央,但是我们这么多图片需要在frame内容面板的各个地方。在 Java 的图形用户界面(GUI)编程里,setBounds 是一个较为常用的方法,它主要用于精确设置组件的位置和大小。
setBounds注意事项:
布局管理器影响:如果使用了布局管理器(如 FlowLayout、BorderLayout 等),setBounds 方法可能不会生效,因为布局管理器会根据自身规则来决定组件的位置和大小。所以使用 setBounds 时要确保关闭布局管理器(设置为 null)。所以我们要取消居中位置。
可移植性问题:使用 setBounds 精确设置组件位置和大小可能会导致在不同的操作系统、屏幕分辨率下显示效果不一致,因为像素的实际物理尺寸可能不同。因此,在实际开发中,建议优先使用布局管理器来管理组件布局。
//取消默认的居中放置,只有取消了才会按照XY轴的形式添加组件
this.setLayout(null);
public void setBounds(int x, int y, int width, int height)
我们现在可以把这15张图片全部添加到程序中,
我们可以用循环的方法将这些图片按照坐标轴分配,用两个for循环完成。
private void initImage() {
int num=1;
//外循环
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
//创建一个JLabel的对象(管理容器)
JLabel label = new JLabel(new ImageIcon("C:\\Users\\小新
\\IdeaProjects\\Test\\dog\\"+num+".png"));
//指定图片位置
label.setBounds(105 *j, 105*i, 105, 105);
this.getContentPane().add(label);
num++;
}
}
这里的ImageIcon和Jlabel类可以合并,类似于链式编程:
JLabel label = new JLabel(new ImageIcon("C:\\Users\\小新\\IdeaProjects\\Test\\dog\\4.png"));
打乱图片:
private void initDate() {
int[][] data = new int[4] [4];
int[] temp = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
Random random = new Random();
for (int i = 0; i < temp.length; i++) {
int index = random.nextInt(temp.length);
int t = temp[i];
temp[i] = temp[index];
temp[index] = t;
}
//遍历一维数组temp得到每一个元素,
// 把每一个元素依次添加到二维数组当中
for (int i = 0; i < temp.length; i++) {
data[i / 4][i % 4] = temp[i];
}
}
接着我们可以美化图片,给图片加上边框已经小小的细节:
我们将图片插入界面中,记住这段代码写在循环的后面,因为先加载的图片在上方,后加载的在下方。
ImageIcon back=new ImageIcon("C:\\Users\\小新\\IdeaProjects\\Test\\back.png");
JLabel label=new JLabel(back);
label.setBounds(40,40,508,560);
this.getContentPane().add(label);
我们可以使用这段代码,使得图片与图片之间出现凹凸的效果。
label.setBorder(new BevelBorder(1));
this.getContentPane().add(label);
移动图片:
移动图片我们首先需要用到KeyListener这个接口,添加这个事件。
1. keyTyped(KeyEvent e)
当按下并释放一个键,即产生一个字符时触发此方法。
2. keyPressed(KeyEvent e)
当按下某个键时触发此方法。
3. keyReleased(KeyEvent e)
当释放某个键时触发此方法。
我们这里移动图片可以用到第三个方法,当我们按下上下左右时,图片可以与之对应。
我们写完这个接口之后,需要重写里面所有的方法。
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
}
@Override
public void keyReleased(KeyEvent e) {
}
在keyReleased方法中,我们就能实现上下左右移动事件。我们的思路是:移动图片就是将空白图片与别的图片与之交换,所以要用二维数组来判断是否为空白图片:
if (temp[i]==0){
x=i/4;
y=i%4;
}
在初始化图片的操作中我们需要清空原本已经出现的所有图片以及刷新界面
private void initImage() {
//清空原本已经出现的所有图片
this.getContentPane().removeAll();
//外循环
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
int num=data[i][j];
//创建一个JLabel的对象(管理容器)
JLabel label = new JLabel(new ImageIcon("C:\\Users\\小新\\IdeaProjects\\Test\\dog\\" + num + ".png"));
//指定图片位置
label.setBounds(105 * j+83, 105 * i+134, 105, 105);
//0:表示让图片凸起来
//1:表示让图片凹下去
label.setBorder(new BevelBorder(1));
this.getContentPane().add(label);
}
}
//先加载的图片在上方,后加载的在下方
ImageIcon back=new ImageIcon("C:\\Users\\小新\\IdeaProjects\\Test\\back.png");
JLabel label=new JLabel(back);
label.setBounds(40,40,508,560);
this.getContentPane().add(label);
//刷新一下界面
this.getContentPane().repaint();
}
this.getContentPane().repaint();
这行代码的主要作用是请求重绘 JFrame
的内容面板。当组件的外观发生变化(如颜色、大小、位置等改变),或者需要更新显示内容时,调用 repaint()
方法可以让系统安排重绘操作,使界面能及时反映出这些变化。
重写键盘监听里面的方法:
我们利用图片在二维数组里的位置来完成上下左右操作,将空白图片与旁边图片进行位置变化。
@Override
public void keyReleased(KeyEvent e) {
//这个方法可以对上下左右进行判断
//左:37 上:38 右:39 下:40
int code = e.getKeyCode();
//向左移动
if (code == 37) {
if(y==3){
return;
}
data[x][y] = data[x][y + 1];
data[x][y + 1] = 0;
y++;
initImage();
//向上移动
} else if (code == 38) {
if(x==3){
return;
}
data[x][y] = data[x + 1][y];
data[x + 1][y] = 0;
x++;
initImage();
//向右移动
} else if (code == 39) {
if(y==0){
return;
}
data[x][y] = data[x][y - 1];
data[x][y - 1] = 0;
y--;
initImage();
} else if (code == 40) {
if(x==0){
return;
}
data[x][y] = data[x - 1][y];
data[x - 1][y] = 0;
x--;
initImage();
}
正常的游戏操作已经完成,接下来我们可以通过键盘监听来完成特定的操作:
下面代码可以按W使得图片变的完整。
if (code==87) {
data=new int[][]{
{1,2,3,4},
{5,6,7,8},
{9,10,11,12},
{13,14,15,0}
};
initImage();
计步与菜单业务实现:
首先实现我们的计步操作,在成员位置先定义一个step,因为我们后面要用到,所以不要写在方法体里面。
int step=0;
在intIamge()方法中加入计步界面。
JLabel stepCont=new JLabel("步数"+step);
stepCont.setBounds(40,20,100,20);
this.getContentPane().add(stepCont);
因为我们每次按上下左右,计步器都应该+1,所以我们把step++放入上下左右方法中
@Override
public void keyReleased(KeyEvent e) {
if (vict()){
return;
}
//这个方法可以对上下左右进行判断
//左:37 上:38 右:39 下:40
int code = e.getKeyCode();
//向左移动
if (code == 37) {
if(y==3){
return;
}
data[x][y] = data[x][y + 1];
data[x][y + 1] = 0;
y++;
step++;
initImage();
//向上移动
} else if (code == 38) {
if(x==3){
return;
}
data[x][y] = data[x + 1][y];
data[x + 1][y] = 0;
x++;
step++;
initImage();
//向右移动
} else if (code == 39) {
if(y==0){
return;
}
data[x][y] = data[x][y - 1];
data[x][y - 1] = 0;
y--;
step++;
initImage();
} else if (code == 40) {
if(x==0){
return;
}
data[x][y] = data[x - 1][y];
data[x - 1][y] = 0;
x--;
step++;
initImage();
我们接着实现菜单界面,因为我们是点击菜单,所以可以使用动作监听,在重新游戏里面完成以下三个方法。注意:计步器清零需要放在第一个不然会出现bug,会导致计步器不会清零,因为在重写加载图片的方法中有计数功能,所以会冲突。
@Override
public void actionPerformed(ActionEvent e) {
Object o = e.getSource();
if (o==replayItem){
//计步器清零
step=0;
//打乱图片顺序
initDate();
//重写加载图片
initImage();
} else if (o==reLoginItem) {
}else if (o==closeItem) {
}else if (o==accountItem) {
}
}
这里需要把在initDate方法中把else去掉,因为这样不管是不是0,都会替代二维数组当时的值
for (int i = 0; i < temp.length; i++) {
if (temp[i]==0){
x=i/4;
y=i%4;
} {
data[i / 4][i % 4] = temp[i];
}
}
菜单界面的完整代码:
@Override
public void actionPerformed(ActionEvent e) {
Object o = e.getSource();
if (o==replayItem){
//计步器清零
step=0;
//打乱图片顺序
initDate();
//重写加载图片
initImage();
} else if (o==reLoginItem) {
//关闭当前游戏界面
this.setVisible(false);
//打开登录界面
new LoginJFrame();
}else if (o==closeItem) {
System.exit(0);
}else if (o==accountItem) {
//创建一个弹窗对象
JDialog jDialog=new JDialog();
//创建一个管理图片的容器对象JLabel
JLabel jLabel=new JLabel(new ImageIcon("C:\\Users\\小新\\IdeaProjects\\Test\\wechat.jpg"));
//设置位置和宽高
jLabel.setBounds(0,0,258,258);
//把图片添加到弹框当中
jDialog.getContentPane().add(jLabel);
//给弹框设置大小
jDialog.setSize(344,344);
//让弹窗置顶
jDialog.setAlwaysOnTop(true);
//让弹窗居中
jDialog.setLocationRelativeTo(null);
//显示弹窗
jDialog.setVisible(true);
}
界面:
游戏界面几乎完整的代码,以下代码还有不足,以及这个游戏还没写完,等学习到足够的知识,会将这个游戏补齐:
import javax.swing.*;
import javax.swing.border.BevelBorder;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Random;
public class GameJFrame extends JFrame implements KeyListener, ActionListener {
int x=0;
int y=0;
int[][] data = new int[4] [4];
int[][] win={
{1,2,3,4},
{5,6,7,8},
{9,10,11,12},
{13,14,15,0}
};
int step=0;
JMenuItem replayItem =new JMenuItem("重新游戏");
JMenuItem reLoginItem =new JMenuItem("重新登录");
JMenuItem closeItem =new JMenuItem("关闭游戏");
JMenuItem accountItem=new JMenuItem("公众号");
public GameJFrame() {
//初始化界面
initJFrame();
//初始化菜单
initJMenuBar();
//初始化数据
initDate();
//初始化图片
initImage();
//显示界面
this.setVisible(true);
}
private void initDate() {
int[] temp = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
Random random = new Random();
for (int i = 0; i < temp.length; i++) {
int index = random.nextInt(temp.length);
int t = temp[i];
temp[i] = temp[index];
temp[index] = t;
}
//遍历一维数组temp得到每一个元素,
// 把每一个元素依次添加到二维数组当中
for (int i = 0; i < temp.length; i++) {
if (temp[i]==0){
x=i/4;
y=i%4;
} {
data[i / 4][i % 4] = temp[i];
}
}
}
//初始化图片
private void initImage() {
//清空原本已经出现的所有图片
this.getContentPane().removeAll();
if (vict()){
JLabel label3=new JLabel(new ImageIcon("C:\\Users\\小新\\IdeaProjects\\Test\\vict.png"));
label3.setBounds(203,283,197,73);
this.getContentPane().add(label3);
}
//外循环
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
int num=data[i][j];
//创建一个JLabel的对象(管理容器)
JLabel label = new JLabel(new ImageIcon("C:\\Users\\小新\\IdeaProjects\\Test\\dog\\" + num + ".png"));
//指定图片位置
label.setBounds(105 * j+83, 105 * i+134, 105, 105);
//0:表示让图片凸起来
//1:表示让图片凹下去
label.setBorder(new BevelBorder(1));
this.getContentPane().add(label);
}
}
//先加载的图片在上方,后加载的在下方
ImageIcon back=new ImageIcon("C:\\Users\\小新\\IdeaProjects\\Test\\back.png");
JLabel label=new JLabel(back);
label.setBounds(40,40,508,560);
this.getContentPane().add(label);
JLabel stepCont=new JLabel("步数"+step);
stepCont.setBounds(40,20,100,20);
this.getContentPane().add(stepCont);
//刷新一下界面
this.getContentPane().repaint();
}
private void initJMenuBar() {
//创建整个的菜单对象
JMenuBar jMenuBar=new JMenuBar();
//创建菜单上面的两个选项的对象(功能 关于我们)
JMenu functionJMenu=new JMenu("功能");
JMenu aboutJMenu=new JMenu("关于我们");
//创建选项下面的条目对象
replayItem.addActionListener(this);
reLoginItem.addActionListener(this);
closeItem.addActionListener(this);
accountItem.addActionListener(this);
//将每一个选项下面的条目添加到选项当中
functionJMenu.add(replayItem);
functionJMenu.add(reLoginItem);
functionJMenu.add(closeItem);
aboutJMenu.add(accountItem);
//将菜单里面的两个选项添加到菜单当中
jMenuBar.add(functionJMenu);
jMenuBar.add(aboutJMenu);
//给整个界面设置菜单
this.setJMenuBar(jMenuBar);
}
private void initJFrame() {
//设置界面的宽高
this.setSize(603, 680);
//设置界面标题
this.setTitle("拼图游戏");
//设置界面置顶
this.setAlwaysOnTop(true);
//设置界面居中
this.setLocationRelativeTo(null);
//设置关闭模式
this.setDefaultCloseOperation(3);
//取消默认的居中放置,只有取消了才会按照XY轴的形式添加组件
this.setLayout(null);
//给整个界面添加键盘监听事件方法
this.addKeyListener(this);
}
@Override
public void keyTyped(KeyEvent e) {
}
//按下不松调用方法
@Override
public void keyPressed(KeyEvent e) {
int code = e.getKeyCode();
if (code==65){
this.getContentPane().removeAll();
JLabel label1=new JLabel(new ImageIcon("C:\\Users\\小新\\IdeaProjects\\Test\\newdog.png"));
label1.setBounds(83,134,420,420);
this.getContentPane().add(label1);
ImageIcon back=new ImageIcon("C:\\Users\\小新\\IdeaProjects\\Test\\back.png");
JLabel label=new JLabel(back);
label.setBounds(40,40,508,560);
this.getContentPane().add(label);
//刷新一下界面
this.getContentPane().repaint();
} else if (code==87) {
data=new int[][]{
{1,2,3,4},
{5,6,7,8},
{9,10,11,12},
{13,14,15,0}
};
initImage();
}
}
@Override
public void keyReleased(KeyEvent e) {
if (vict()){
return;
}
//这个方法可以对上下左右进行判断
//左:37 上:38 右:39 下:40
int code = e.getKeyCode();
//向左移动
if (code == 37) {
if(y==3){
return;
}
data[x][y] = data[x][y + 1];
data[x][y + 1] = 0;
y++;
step++;
initImage();
//向上移动
} else if (code == 38) {
if(x==3){
return;
}
data[x][y] = data[x + 1][y];
data[x + 1][y] = 0;
x++;
step++;
initImage();
//向右移动
} else if (code == 39) {
if(y==0){
return;
}
data[x][y] = data[x][y - 1];
data[x][y - 1] = 0;
y--;
step++;
initImage();
} else if (code == 40) {
if(x==0){
return;
}
data[x][y] = data[x - 1][y];
data[x - 1][y] = 0;
x--;
step++;
initImage();
} else if (code==65) {
initImage();
}
}
public Boolean vict(){
for (int i = 0; i < data.length; i++) {
for (int j=0;j<data.length;j++){
if(data[i][j]!=win[i][j]){
return false;
}
}
}
return true;
}
@Override
public void actionPerformed(ActionEvent e) {
Object o = e.getSource();
if (o==replayItem){
//计步器清零
step=0;
//打乱图片顺序
initDate();
//重写加载图片
initImage();
} else if (o==reLoginItem) {
//关闭当前游戏界面
this.setVisible(false);
//打开登录界面
new LoginJFrame();
}else if (o==closeItem) {
System.exit(0);
}else if (o==accountItem) {
//创建一个弹窗对象
JDialog jDialog=new JDialog();
//创建一个管理图片的容器对象JLabel
JLabel jLabel=new JLabel(new ImageIcon("C:\\Users\\小新\\IdeaProjects\\Test\\wechat.jpg"));
//设置位置和宽高
jLabel.setBounds(0,0,258,258);
//把图片添加到弹框当中
jDialog.getContentPane().add(jLabel);
//给弹框设置大小
jDialog.setSize(344,344);
//让弹窗置顶
jDialog.setAlwaysOnTop(true);
//让弹窗居中
jDialog.setLocationRelativeTo(null);
//显示弹窗
jDialog.setVisible(true);
}
}
}