做一个有趣一点的项目,一方面提升编程思维,提升编程能力。同时开发一个实战的游戏,可以跟学java的人员拉开距离。
游戏项目开发的思路:
1.GUI界面编程 :使用Swing技术
2.二维数组
3.程序流程控制:因为在业务的推进过程中,肯定会涉及到一些循环的操作。再包括比如把拼图顺序打乱,肯定需要程序流程控制,循环。
4.面向对象编程:这个界面需要去继承一个Jframe成为一个窗口对象。他们是需要我们的一个面向对象编程的语法来理解的,包括这里面的事件监听器上下左右。给他绑定匿名内部类对象多需要一些匿名内部类的知识。
准备环节
- 创建一个模块用于开发石头迷阵游戏,模块名称取名为:stone-maze
- 导入项目需要的资源包到src目录下:主要是一些图片文件,在image文件夹下。
- 创建项目包:com.itheima.
一、创建石头迷阵界面
- 定义主界面类,MainFrame继承JFrame.
独立功能独立成方法,在MainFrame.java中不仅要初始化窗口,还要初始化界面,初始化菜单。分三块功能进行制作,所以不要在这里直接写这个代码,最好在无参构造器里面。 当别人调用无参构造器时在其里面进行初始化。这样的话程序比较逻辑清晰。代码复用性比较高,比较优雅。
- 初始化窗口大小
窗口大小要根据图片的大小和实际项目的需求来进行设定。注意:显示窗口一定要设置到最后,当日常AI生成的代码没有运行没有显示出时,有可能就是这个问题。
- 初始化界面图片
- 初始化界面菜单:系统退出,重启游戏。
二、打乱顺序
打乱界面的图片顺序,让游戏具备可玩性:使用方法如下
打乱二维数组中的元素顺序:initRandomArray();
完成代码如下:
package com.itheima;
import javax.swing.*;
//自定义窗口类,创建对象,展示一个主窗口。
public class MainFrame extends JFrame {
//设置图片位置,将图片位置设置为常量,防止后期改模块名而找不到图片位置
private static final String imagePath = "stone-maze/src/image/";
//准备一个数组。用户存储数字色块的行列位置:4行4列
private int[][] imageData = {
{1,2,3,4},
{5,6,7,8},
{9,10,11,12},
{13,14,15,0}
};
public MainFrame() {
//1.调用一个初始化方法:初始化窗口大小等信息
initFrame();
//4.打乱数组色块的顺序,再展示图片
initRandomArray();
//2.初始化界面,展示数字色块。
initImage();
//3.初始化系统菜单,点击弹出菜单信息是系统退出,重启游戏
initMenu();
//设置窗口的显示.注意显示窗口一定要设置到最后
this.setVisible(true);
}
private void initRandomArray() {
//打乱二维数组中的元素顺序
for (int i = 0; i < imageData.length; i++) {
for (int j = 0; j < imageData[i].length; j++) {
//随机两个行列位置,让两个位置交换
int i1 = (int)(Math.random()*imageData.length);
int j1 = (int)(Math.random()*imageData.length);
int i2 = (int)(Math.random()*imageData.length);
int j2 = (int)(Math.random()*imageData.length);
int temp = imageData[i1][j1];
imageData[i1][j1] = imageData[i2][j2];
imageData[i2][j2] = temp;
}
}
}
private void initMenu() {
JMenuBar menuBar = new JMenuBar();//创建一个菜单条
JMenu menu = new JMenu("系统");//创建一个菜单
JMenuItem exitJi = new JMenuItem("退出");
menu.add(exitJi);//添加一个菜单项
exitJi.addActionListener(e -> {
dispose();//销毁!
});
//添加一个菜单,重启
JMenuItem restartJi = new JMenuItem("重启");
menu.add(restartJi);
restartJi.addActionListener(e -> {
//重启游戏。
});
menuBar.add(menu);//添加到菜单条中
this.setJMenuBar(menuBar);
}
private void initImage() {
//1.展示一个行列矩阵的图片色块依次铺满窗口(4*4)
for (int i = 0; i < imageData.length; i++) {
for (int j = 0; j < imageData[i].length; j++) {
//2.拿到图片名称
String imageName = imageData[i][j] + ".png";
//2.创建一个JLabel对象,设置图片给他展示
JLabel label = new JLabel();
//3.设置图片到label对象中去
label.setIcon(new ImageIcon(imagePath + imageName));
//4.设置图片展示位置
label.setBounds(20+j * 100, 60+i * 100, 100, 100);
//5.把这个图片展示到窗口上去,this代表当前窗口主对象
this.add(label);
}
}
//设置窗口的背景图片(背景图片太小了所以没展示)
JLabel background = new JLabel(new ImageIcon(imagePath + "background.png"));
background.setBounds(0, 0, 450, 484);
this.add(background);
}
private void initFrame() {
//设置窗口的标题
this.setTitle("石子迷阵 V 1.0 xuan");
//设置窗口的宽高
this.setSize(465, 575);
//设置窗口关闭方式
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//设置窗口居中
this.setLocationRelativeTo(null);
//设置布局方式为绝对位置定位,一定要设置绝对布局,用于展示图片在坐标上的位置
this.setLayout(null);
}
}
package com.itheima;
public class App {
public static void main(String[] args) {
new MainFrame();
}
}
三、控制上下左右移动
- 给窗口绑定上下左右按键事件
- 控制位置的交换
——定位当前空白色块的位置
——根据用户点击的方位确定交换哪个数据,到数组中交换
- 重新绘制主界面的内容
——让主界面按照二维数组的最新内容刷新界面
//5.给当前窗口绑定上下左右按键事件。
initKeyPressEvent();
private void initKeyPressEvent() {
//给当前窗口绑定上下左右按键事件。
this.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
//获取当前按钮的编号
int keyCode = e.getKeyCode();
//判断这个编号是否是上下左右的按键
switch (keyCode) {
case KeyEvent.VK_UP:
//用户按了上建,让图片向上移动。
switchAndMove(Direction.UP);
break;
case KeyEvent.VK_DOWN:
//用户按了下建,让图片向下移动。
switchAndMove(Direction.DOWN);
break;
case KeyEvent.VK_LEFT:
//用户按了左建,让图片向左移动。
switchAndMove(Direction.LEFT);
break;
case KeyEvent.VK_RIGHT:
//用户按了右建,让图片向右移动。
switchAndMove(Direction.RIGHT);
break;
}
}
});
}
package com.itheima;
//使用枚举来定义方向,使项目更加专业
public enum Direction {
UP,DOWN,LEFT,RIGHT;
}
四、判断是否通关
- 用户每操作一步,需要立即判断是否已经通过,如果通过,需要显示胜利的标记。
//判断是否赢了。我们设置只要用户刷新界面就要判断一次用户是否胜利。
//且将图层设置在所有界面的上面
if (isWin()) {
//展示胜利的图片
JLabel label = new JLabel(new ImageIcon(imagePath + "win.png"));
label.setBounds(124, 230, 266, 88);
this.add(label);
}
//1.展示一个行列矩阵的图片色块依次铺满窗口(4*4)
for (int i = 0; i < imageData.length; i++) {
for (int j = 0; j < imageData[i].length; j++) {
//2.拿到图片名称
String imageName = imageData[i][j] + ".png";
//2.创建一个JLabel对象,设置图片给他展示
JLabel label = new JLabel();
//3.设置图片到label对象中去
label.setIcon(new ImageIcon(imagePath + imageName));
//4.设置图片展示位置
label.setBounds(20+j * 100, 60+i * 100, 100, 100);
//5.把这个图片展示到窗口上去,this代表当前窗口主对象
this.add(label);
}
}
//设置窗口的背景图片(背景图片太小了所以没展示)
JLabel background = new JLabel(new ImageIcon(imagePath + "background.png"));
background.setBounds(0, 20, 450, 484);
this.add(background);
//刷新新图层,重新绘制
this.repaint();
private boolean isWin() {
//判断游戏二维数组和赢了之后的二维数组的内容是否一样,只要有一个位置处的数据不一样,说明没有赢了
for (int i = 0; i < imageData.length; i++) {
for (int j = 0; j < imageData[i].length; j++) {
if (imageData[i][j] != winData[i][j]) {
return false;
}
}
}
//赢了
return true;
}
五、统计移动步骤、重启游戏
- 没成功移动一步,都需要累加一次步数
- 定义一个变量用于累加步数,并实时展示到界面上
private int count;//统计总共移动的步数。
//先清空窗口上的全部图层
this.getContentPane().removeAll();
//刷新界面时,可以给界面显示步数。
//给窗口添加一个展示文字的组件
JLabel countTxt = new JLabel("当前移动" + count + "步");
countTxt.setBounds(20, 20, 100, 20);
//把文字展示成红色
countTxt.setForeground(Color.RED);
//加粗
countTxt.setFont(new Font("楷体", Font.BOLD, 12));
this.add(countTxt);
六、拓展问题
数字华容道的乱序操作,并不是可以随意打乱的,必须满足一定的算法去打乱顺序,这样才是有解的,才能让玩家恢复到有序。有没有简单的算法????