1. 设计游戏的目的
锻炼逻辑思维能力
利用Java的图形化界面,写一个项目,知道前面学习的知识点在实际开发中的应用场景。
2. 游戏的最终效果呈现
3. 实现思路
先写游戏主界面,实现步骤如下:
1,完成最外层窗体的搭建。
2,再把菜单添加到窗体当中。
3,把小图片添加到窗体当中。
4,打乱数字图片的顺序。
5,让数字图片可以移动起来。
6,通关之后的胜利判断。
7,添加其他额外的功能。
4.主界面设计
//1.召唤主界面
JFrame jFrame = new JFrame();
//2.设置主界面的大小
jFrame.setSize(514,595);
//3.让主界面显示出来
jFrame.setVisible(true);
//1.召唤主界面
JFrame jFrame = new JFrame();
//设置主界面的大小
jFrame.setSize(514,595);
//将主界面设置到屏幕的正中央
jFrame.setLocationRelativeTo(null);
//将主界面置顶
jFrame.setAlwaysOnTop(true);
//关闭主界面的时候让代码一起停止
jFrame.setDefaultCloseOperation(3);
//给主界面设置一个标题
jFrame.setTitle("拼图游戏单机版 v1.0");
//2.让主界面显示出来
jFrame.setVisible(true);
注意事项:
jFrame.setVisible(true);必须要写在最后一行。
5. 利用继承简化代码
如果把所有的代码都写在main方法中,那么main方法里面的代码,就包含游戏主界面的代码,登录界面的代码,注册界面的代码,会变得非常臃肿后期维护也是一件非常难的事情,所以我们需要用继承改进,改进之后,代码就可以分类了。
//登录界面
public class LoginJFrame extends JFrame {
//LoginJFrame 表示登录界面
//以后所有跟登录相关的代码,都写在这里
public LoginJFrame(){
//在创建登录界面的时候,同时给这个界面去设置一些信息
//比如,宽高,直接展示出来
this.setSize(488,430);
//设置界面的标题
this.setTitle("拼图 登录");
//设置界面置顶
this.setAlwaysOnTop(true);
//设置界面居中
this.setLocationRelativeTo(null);
//设置关闭模式
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
//让显示显示出来,建议写在最后
this.setVisible(true);
}
}
//注册界面
public class RegisterJFrame extends JFrame {
//跟注册相关的代码,都写在这个界面中
public RegisterJFrame(){
this.setSize(488,500);
//设置界面的标题
this.setTitle("拼图 注册");
//设置界面置顶
this.setAlwaysOnTop(true);
//设置界面居中
this.setLocationRelativeTo(null);
//设置关闭模式
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
//让显示显示出来,建议写在最后
this.setVisible(true);
getContentPane();
}
}
//游戏主界面
public class GameJFrame extends JFrame {
public GameJFrame() {
//设置界面的宽高
this.setSize(603, 680);
//设置界面的标题
this.setTitle("拼图单机版 v1.0");
//设置界面置顶
this.setAlwaysOnTop(true);
//设置界面居中
this.setLocationRelativeTo(null);
//设置关闭模式
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
//取消默认的居中放置,只有取消了才会按照XY轴的形式添加组件
this.setLayout(null);
//让界面显示出来,建议写在最后
this.setVisible(true);
}
}
6. 菜单制作
6.1菜单的组成
初始界面 在菜单中有:JMenuBar、JMenu、JMenuItem三个角色。
JMenuBar:如上图中红色边框
JMenu:如上图蓝色边框
JMenuItem:如上图绿色字体处
其中JMenuBar是整体,一个界面中一般只有一个JMenuBar。
而JMenu是菜单中的选项,可以有多个。
JMenuItem是选项下面的条目,也可以有多个。
6.2代码书写步骤
1,创建JMenuBar对象
2,创建JMenu对象
3,创建JMenuItem对象
4,把JMenuItem添加到JMenu中
5,把JMenu添加到JMenuBar中
6,把整个JMenuBar设置到整个界面中
//创建一个菜单对象
JMenuBar jMenuBar = new JMenuBar();
//设置菜单的宽高
jMenuBar.setSize(514, 20);
//创建一个选项
JMenu jMenu1 = new JMenu("功能");
//创建一个条目
jMenuItem1 = new JMenuItem("重新游戏");
//把条目添加到选项当中
jMenu1.add(jMenuItem1);
//把选项添加到菜单当中
jMenuBar.add(jMenu1);
//把菜单添加到最外层的窗体当中
this.setJMenuBar(jMenuBar);
7.添加图片
在上图中,其实是15张小图片。我们在添加图片的时候,要把添加图片的操作重复15次,才能把所有图片都添加到界面当中。
7.1使用到的Java类
ImageIcon:描述图片的类,可以关联计算中任意位置的图片。
但是一般会把图片拷贝到当前项目中。
JLabel:用来管理图片,文字的类。
可以用来设置位置,宽高。
7.2位置坐标
界面左上角的点可以看做是坐标的原点,横向的是X轴,纵向的是Y轴。
图片的位置其实取决于图片左上角的点,在坐标中的位置。
如果是(0,0)那么该图片会显示再屏幕的左上角。
8.3添加步骤:
1,取消整个界面的默认居中布局
2,创建ImageIcon对象,并制定图片位置。
3,创建JLabel对象,并把ImageIcon对象放到小括号中。
4,利用JLabel对象设置大小,宽高。
5,将JLabel对象添加到整个界面当中。
//1,先对整个界面进行设置
//取消内部居中放置方式
this.setLayout(null);
//2,创建ImageIcon对象,并制定图片位置。
ImageIcon imageIcon1 = new ImageIcon("image\\1.png");
//3,创建JLabel对象,并把ImageIcon对象放到小括号中。
JLabel jLabel1 = new JLabel(imageIcon1);
//4,利用JLabel对象设置大小,宽高。
jLabel1.setBounds(0, 0, 100, 100);
//5,将JLabel对象添加到整个界面当中。
this.add(jLabel1);
7.4 打乱图片的位置
每一张图片都对应1~15之间的数字,空白处为0,打乱图片实际上就是把数字打乱,添加图片的时候按照打乱的图片添加即可
7.4.1 打乱数组中数据的练习
int[] tempArr = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
要求:打乱一维数组中的数据,并按照4个一组的方式添加到二维数组中。
public class Test1 {
public static void main(String[] args) {
//需求:
//把一个一维数组中的数据:0~15 打乱顺序
//然后再按照4个一组的方式添加到二维数组当中
//1.定义一个一维数组
int[] tempArr = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
//2.打乱数组中的数据的顺序
//遍历数组,得到每一个元素,拿着每一个元素跟随机索引上的数据进行交换
Random r = new Random();
for (int i = 0; i < tempArr.length; i++) {
//获取到随机索引
int index = r.nextInt(tempArr.length);
//拿着遍历到的每一个数据,跟随机索引上的数据进行交换
int temp = tempArr[i];
tempArr[i] = tempArr[index];
tempArr[index] = temp;
}
//3.遍历数组
for (int i = 0; i < tempArr.length; i++) {
System.out.print(tempArr[i] + " ");
}
System.out.println();
//4.创建一个二维数组
int[][] data = new int[4][4];
//5.给二维数组添加数据
//解法一:
//遍历一维数组tempArr得到每一个元素,把每一个元素依次添加到二维数组当中
for (int i = 0; i < tempArr.length; i++) {
data[i / 4][i % 4] = tempArr[i];
}
//遍历二维数组
for (int i = 0; i < data.length; i++) {
for (int j = 0; j < data[i].length; j++) {
System.out.print(data[i][j] + " ");
}
System.out.println();
}
}
}
7.4.2 打乱图片
public class GameJFrame extends JFrame {
//JFrame 界面,窗体
//子类呢?也表示界面,窗体
//规定:GameJFrame这个界面表示的就是游戏的主界面
//以后跟游戏相关的所有逻辑都写在这个类中
//创建一个二维数组
//目的:用来管理数据
//加载图片的时候,会根据二维数组中的数据进行加载
int[][] data = new int[4][4];
public GameJFrame() {
//初始化界面
initJFrame();
//初始化菜单
initJMenuBar();
//初始化数据(打乱)
initData();
//初始化图片(根据打乱之后的结果去加载图片)
initImage();
//让界面显示出来,建议写在最后
this.setVisible(true);
}
//初始化数据(打乱)
private void initData() {
//1.定义一个一维数组
int[] tempArr = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
//2.打乱数组中的数据的顺序
//遍历数组,得到每一个元素,拿着每一个元素跟随机索引上的数据进行交换
Random r = new Random();
for (int i = 0; i < tempArr.length; i++) {
//获取到随机索引
int index = r.nextInt(tempArr.length);
//拿着遍历到的每一个数据,跟随机索引上的数据进行交换
int temp = tempArr[i];
tempArr[i] = tempArr[index];
tempArr[index] = temp;
}
//4.给二维数组添加数据
//遍历一维数组tempArr得到每一个元素,把每一个元素依次添加到二维数组当中
for (int i = 0; i < tempArr.length; i++) {
data[i / 4][i % 4] = tempArr[i];
}
}
//初始化图片
//添加图片的时候,就需要按照二维数组中管理的数据添加图片
private void initImage() {
//外循环 --- 把内循环重复执行了4次。
for (int i = 0; i < 4; i++) {
//内循环 --- 表示在一行添加4张图片
for (int j = 0; j < 4; j++) {
//获取当前要加载图片的序号
int num = data[i][j];
//创建一个JLabel的对象(管理容器)
JLabel jLabel = new JLabel(new ImageIcon("image\\animal\\animal3\\" + num + ".jpg"));
//指定图片位置
jLabel.setBounds(105 * j, 105 * i, 105, 105);
//把管理容器添加到界面中
this.getContentPane().add(jLabel);
}
}
}
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("拼图单机版 v1.0");
//设置界面置顶
this.setAlwaysOnTop(true);
//设置界面居中
this.setLocationRelativeTo(null);
//设置关闭模式
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
//取消默认的居中放置,只有取消了才会按照XY轴的形式添加组件
this.setLayout(null);
}
}
8. 事件
事件是可以被组件识别的操作。
8.1 常见的三个核心要素
事件源: 按钮 图片 窗体…
事件:某些操作
绑定监听:当事件源上发生了某个事件,则执行某段代码
8.2 常见的三种事件监听
键盘监听 KeyListener
鼠标监听 MouseListener
动作监听 ActionListener
8.3 动作监听
包含:
鼠标左键点击
空格
9.3.1 事件的三种实现方式
定义实现类实现接口
匿名内部类
本类实现接口
9.美化界面
界面搭建好之后,就需要美化界面了,本次需要美化下面四个地方:
-
将15张小图片移动到界面的中央偏下方
-
添加背景图片
-
添加图片的边框
-
优化路径
9.1 小图片居中
原本的小图片,都在左上角的位置,不好看,我想让他们居中,这样就需要给每一张图片在x和y都进行一个偏移即可。
//细节:
//先加载的图片在上层
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
int num=data[i][j];
//创建一个JLable的对象
JLabel jLabel=new JLabel(new ImageIcon(path+num+".jpg"));
//指定图片位置
jLabel.setBounds(105*j+83,105*i+134,105,105);
//给图片添加边框
jLabel.setBorder(new BevelBorder(0));
//把管理容器添加到容器界面中
this.getContentPane().add(jLabel);
}
}
9.2 添加背景图片
//添加背景图片
ImageIcon bg=new ImageIcon("image\\background.png");
JLabel background=new JLabel(bg);
background.setBounds(40,40,508,560);
this.getContentPane().add(background);
9.3 优化路径
9.3.1 计算机中的两种路径
绝对路径
从判断开始的路径,此时路径是固定的。
C:\\a.txt
相对路径
没有从判断开始的路径
aaa\\bbb\\a.txt
10. 上下左右移动的逻辑
上下左右的我们看上去就是移动空白的方块,实则逻辑跟我们看上去的相反:
- 上移:是把空白区域下方的图片上移。
- 下移:是把空白区域上方的图片下移。
- 左移:是把空白区域右方的图片左移。
- 右移:是把空白区域左方的图片右移。
但是在移动的时候也有一些小问题要注意:
- 如果空白区域已经在最上面了,此时x=0,那么就无法再下移了。
- 如果空白区域已经在最下面了,此时x=3,那么就无法再上移了。
- 如果空白区域已经在最左侧了,此时y=1,那么就无法再右移了。
- 如果空白区域已经在最右侧了,此时y=3,那么就无法再左移了。
实现步骤:
- 本类实现KeyListener接口,并重写所有抽象方法
- 给整个界面添加键盘监听事件
- 统计一下空白方块对应的数字0在二维数组中的位置
- 在keyReleased方法当中实现移动的逻辑
//对上、下、左、右进行判断
//左: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();
}
11. 查看完整图片的功能
在玩游戏的过程中,我想看一下最终的效果图,该怎么办呢?
此时可以添加一个功能,当我们长按某个键(假设为A),不松的时候,就显示完整图片,松开就显示原来的图片
实现步骤:
- 给整个界面添加键盘事件
- 在keyPressed中书写按下不松的逻辑
- 在keyReleased中书写松开的逻辑
//按下不松
@Override
public void keyPressed(KeyEvent e) {
int code=e.getKeyCode();
if(code==65){
this.getContentPane().removeAll();
//加载图片
JLabel all=new JLabel(new ImageIcon(path+"all.jpg"));
all.setBounds(83,134,420,420);
this.getContentPane().add(all);
//添加背景
ImageIcon bg=new ImageIcon("image\\background.png");
JLabel background=new JLabel(bg);
background.setBounds(40,40,508,560);
this.getContentPane().add(background);
//刷新界面
this.getContentPane().repaint();
}
}
12. 作弊码
不想玩了,想要一键通关
实现步骤:
- 给整个界面添加键盘事件
- 在keyReleased中书写松开的逻辑,当按下W的时候一键通关。
else if(code==87){
data=new int[][]{
{1,2,3,4},
{5,6,7,8},
{9,10,11,12},
{13,14,15,0}
};
initImage();
}
13. 判断胜利
当游戏的图标排列正确了,需要有胜利图标显示。
每次上下左右移动图片的时候都需要进行判断。
在keyReleased中方法一开始的地方就需要写判断是否胜利
实现步骤:
- 定义一个正确的二维数组win。
- 在加载图片之前,先判断一下二维数组中的数字跟win数组中是否相同。
- 如果相同展示正确图标。
- 如果不同则不展示正确图标。
//判断data数组中的数据是否跟win数组中相同
public boolean victory(){
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;
}
14. 计步功能
左上角的计步器,每移动一次,计步器就需要自增一次
实现步骤:
- 定义一个变量用来统计已经玩了多少步。
- 每次按上下左右的时候计步器自增一次即可。
JLabel stepCount=new JLabel("步数"+step);
stepCount.setBounds(50,30,100,20);
this.getContentPane().add(stepCount);
在对上、下、左、右进行判断的代码中,每运行一次step++。
15. 其他功能
完成重新开始、关闭游戏、关于我们。这三个都是在菜单上的,所以可以一起完成。
重新开始:点击之后,重新打乱图片,计步器清零。
关闭游戏:点击之后,全部关闭。
关于我们:点击之后出现CSND博客二维码。
实现步骤:
- 给菜单上的每个选项添加点击事件。
- 在actionPerformed方法中实现对应的逻辑即可。
public void actionPerformed(ActionEvent e) {
//获取当前被点击的条目对象
Object obj=e.getSource();
//判断
if(obj==replayItem){
//再次打乱数据
initData();
//计步器清零
step=0;
//重新加载图片
initImage();
}else if(obj==reLoginItem){
//重新登录
//关闭当前游戏界面
this.setVisible(false);
//打开登录界面
new LoginJFrame();
}else if(obj==closeItem){
//关闭游戏
System.exit(0);
}else if(obj==accountItem){
//创建弹框对象
JDialog jDialog=new JDialog();
JLabel jLabel=new JLabel(new ImageIcon("image\\about.jpg"));
jLabel.setBounds(0,0,258,258);
//把图片添加到弹框中
jDialog.getContentPane().add(jLabel);
//设置弹框大小
jDialog.setSize(344,344);
//让弹框置顶
jDialog.setAlwaysOnTop(true);
//让弹框居中
jDialog.setLocationRelativeTo(null);
//弹框不关闭无法操作下面界面
jDialog.setModal(true);
//让弹框显示出来
jDialog.setVisible(true);
}
}
16.登录界面
16.1 所需要的技术点
第一排:用户名文字其实是一张图片,还是用JLabel去管理ImageIcon
输入框:JTextField(明文显示的输入框)
第二排:密码文字其实是一张图片,还是用JLabel去管理ImageIcon
输入框:JPasswordField(密文显示的输入框)
第三排:验证码文字其实是一张图片,还是用JLabel去管理ImageIcon
输入框:JTextField(明文显示的输入框)
验证码wyS7i:用JLabel去管理文字,需要自己写一个生成验证码的工具类。
第四排:两个都是按钮,绿色跟红色是按钮的背景图
当点击按钮不松的时候,按钮变灰,其实就是换一个深色的背景图。
16.2 分析业务逻辑
1,界面搭建。代码不需要大家写,大家主要完成里面的业务逻辑即可。界面搭建的代码在作业当中已经给出。
2,用静态代码块准备一些初始的用户信息
3,点击登陆按钮之后的逻辑:
- 按下登陆不松,切换登陆按钮的背景图片
- 松开登陆按钮,逻辑较为复杂
- 获取用户输入的用户名,密码,验证码。
- 先比较验证码(正确 错误)
- 判断用户名和密码是否为空,只要有一个为空就不行
- 细节:如果用户没有输入用户名和密码,在代码中获取的不是null,而是长度为0的字符串
- 用户名,密码比较正确,显示登陆成功跳转游戏界面
- 用户名,密码比较错误,提示错误
4,点击注册按钮之后的逻辑
- 暂时不需要写逻辑,后面学习完IO的时候再补
5,点击验证之后
- 更换一个新的验证码(写一个工具类提供验证码)
16.3 添加组件
public void initView() {
//1. 添加用户名文字
JLabel usernameText = new JLabel(new ImageIcon("puzzlegame\\image\\login\\用户名.png"));
usernameText.setBounds(116, 135, 47, 17);
this.getContentPane().add(usernameText);
//2.添加用户名输入框
username.setBounds(195, 134, 200, 30);
this.getContentPane().add(username);
//3.添加密码文字
JLabel passwordText = new JLabel(new ImageIcon("puzzlegame\\image\\login\\密码.png"));
passwordText.setBounds(130, 195, 32, 16);
this.getContentPane().add(passwordText);
//4.密码输入框
password.setBounds(195, 195, 200, 30);
this.getContentPane().add(password);
//验证码提示
JLabel codeText = new JLabel(new ImageIcon("puzzlegame\\image\\login\\验证码.png"));
codeText.setBounds(133, 256, 50, 30);
this.getContentPane().add(codeText);
//验证码的输入框
code.setBounds(195, 256, 100, 30);
this.getContentPane().add(code);
String codeStr = CodeUtil.getCode();
//设置内容
rightCode.setText(codeStr);
//绑定鼠标事件
rightCode.addMouseListener(this);
//位置和宽高
rightCode.setBounds(300, 256, 50, 30);
//添加到界面
this.getContentPane().add(rightCode);
//5.添加登录按钮
login.setBounds(123, 310, 128, 47);
login.setIcon(new ImageIcon("puzzlegame\\image\\login\\登录按钮.png"));
//去除按钮的边框
login.setBorderPainted(false);
//去除按钮的背景
login.setContentAreaFilled(false);
//给登录按钮绑定鼠标事件
login.addMouseListener(this);
this.getContentPane().add(login);
//6.添加注册按钮
register.setBounds(256, 310, 128, 47);
register.setIcon(new ImageIcon("puzzlegame\\image\\login\\注册按钮.png"));
//去除按钮的边框
register.setBorderPainted(false);
//去除按钮的背景
register.setContentAreaFilled(false);
//给注册按钮绑定鼠标事件
register.addMouseListener(this);
this.getContentPane().add(register);
//7.添加背景图片
JLabel background = new JLabel(new ImageIcon("puzzlegame\\image\\login\\background.png"));
background.setBounds(0, 0, 470, 390);
this.getContentPane().add(background);
}
16.4 登录按钮
@Override
public void mousePressed(MouseEvent e) {
if (e.getSource() == login) {
login.setIcon(new ImageIcon("image\\login\\登录按下.png"));
} else if (e.getSource() == register) {
register.setIcon(new ImageIcon("image\\login\\注册按下.png"));
}
}
//松开按钮
@Override
public void mouseReleased(MouseEvent e) {
if (e.getSource() == login) {
login.setIcon(new ImageIcon("image\\login\\登录按钮.png"));
} else if (e.getSource() == register) {
register.setIcon(new ImageIcon("image\\login\\注册按钮.png"));
}
}
标签:界面,拼图,int,小游戏,new,JLabel,添加,图片
From: https://blog.csdn.net/Directionboy/article/details/140621276