java拼图小游戏

java拼图小游戏

  • 拼图游戏
    • 开始游戏
    • 开始游戏后,鼠标操作图片块
    • 【游戏小BUG:java.lang.reflect.InvocationTargetException】
    • 问题原因
    • 解决办法
    • 有待完善功能
    • 完整代码

拼图游戏

参考:传智播客教辅平台 --> 公开课 -->《java基础案例-拼图小游戏3h》

  • 实现窗体的制作(javaFx技术),主窗体PuzzleMain
  • 自己制作的面板类PuzzlePane
  • 图片按照一定的形状进行曲线切割成小图块,图块类 Piece

开始游戏

将小图块设置为活动的(漂浮起来),然后按照在一定区域内将小图块随机分配位置显示,,,,图片归位后设置活动实效

开始游戏后,鼠标操作图片块

监听鼠标三个响应事件

  1. 当鼠标按下时:记录当前的图片位置(为了做移动判断,当鼠标拖动时候,相对拖动比较小的时候不更新位置坐标)
  2. 当鼠标拖动时:当拖动鼠标大于一定的范围时候,就更新小图块的位置坐标
  3. 当鼠标抬起时:(图片块的位置的验证)判断是否归位,,,上下左右10个像素的误差,吸附效果:就是在归位的时候让图片活动实效

游戏的难点:图片的分割(切割的形状的制作凸起和凹陷)和打乱后的归位

【游戏小BUG:java.lang.reflect.InvocationTargetException】

Exception in Application start method
java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:389)
	at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:328)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:873)
Caused by: java.lang.RuntimeException: Exception in Application start method
	at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:917)
	at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$1(LauncherImpl.java:182)
	at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.NullPointerException: Input stream must not be null
	at javafx.scene.image.Image.validateInputStream(Image.java:1128)
	at javafx.scene.image.Image.<init>(Image.java:706)
	at com.guo.puzzle.PuzzleMain.cretePane(PuzzleMain.java:83)  *******************
	at com.guo.puzzle.PuzzleMain.start(PuzzleMain.java:54)         ********************   
	at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$8(LauncherImpl.java:863)
	at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$7(PlatformImpl.java:326)
	at com.sun.javafx.application.PlatformImpl.lambda$null$5(PlatformImpl.java:295)
	at java.security.AccessController.doPrivileged(Native Method)
	at com.sun.javafx.application.PlatformImpl.lambda$runLater$6(PlatformImpl.java:294)
	at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
	at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
	at com.sun.glass.ui.win.WinApplication.lambda$null$3(WinApplication.java:177)
	... 1 more
Exception running application com.guo.puzzle.PuzzleMain

Process finished with exit code 1

问题原因

//创建一个图片对象:Image    ,,通过类加载器,然后在src下的文件夹下找指定的文件,当成一个输入流,然后把这个输入流当做一个参数传给Image的构造方法
                img = new Image(this.getClass().getClassLoader().getResourceAsStream("images/" + fileName + ".jpg"));

反射器的获取图片的路径问题;

解决办法

需要把(.class文件删除),,,,目的是让IDEA从新编译,,,(找到项目所在的磁盘文件夹,out文件夹删除,然后去idea从新编译运行就解决了)

也不知道咋肥事儿,修改(更换)图片后,不删除.class文件,就会找不到文件,,也不知道为啥idea不从新编译,,非要删除.class后才重新编译

java拼图小游戏_第1张图片

(一个普通java项目编译之后会产生bin目录用来存储编译好的 .class 文件,而intellij idea 的目录名称默认为out,同样也是在项目的根路径下. )

小提醒:添加/或者更换项目中images中的图片时,记得删除编译文件夹out,然后重新编译

有待完善功能

游戏结束时,没有友好的提示!
游戏没有设置难度等级(图片的曲线切割是按照100*100切割的,添加大一点图片,切割的图片块就会变多,从而增加难度)
游戏中没有设置计分机制,,,每个图片归位后加分,
游戏中过于单调没有背景音乐

完整代码

java拼图小游戏_第2张图片

类图:
java拼图小游戏_第3张图片

  1. 实现窗体的制作(javaFx技术),主窗体PuzzleMain
package com.guo.puzzle;
/*
    使用javafx技术
 */

import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.Duration;

import java.util.ArrayList;
import java.util.List;

/**
 * 继承抽象类Application,,,实现抽象方法
 */
public class PuzzleMain extends Application {
    //1.包含了多个图片菜单的Menu对象,,,被多个成员属性访问
    public Menu loadMenu;
    //2.存储所有图块的集合
    private List<Piece> pieceList = new ArrayList<>();
    //3.主窗口的borderPan
    private BorderPane borderPane;
    //4.主舞台对象
    private Stage primaryStage;
    //5.动画对象
    private Timeline timeline;
    //6.当前窗体的puzzlePane对象
    PuzzlePane puzzlePane;

    /**
     * Start()方法我们编写:窗体的主要代码
     *
     * @param primaryStage
     * @throws Exception
     */
    @Override
    public void start(Stage primaryStage) throws Exception {
        this.primaryStage = primaryStage;
        //1.主窗体,,,,布局管理器,,,标题栏,菜单,图片的主面板
        //创建一个BorderPane----布局管理器(上下左右中),但我们只用到:上中
        borderPane = new BorderPane();
        //设置菜单  放在borderPane的“上部”
        borderPane.setTop(createMenu());

        //设置下测的面板
        borderPane.setCenter(cretePane());

        //2.创建一个场景
        //Scene scene = new Scene(borderPane,500,500);//可以自己设置宽高
        Scene scene = new Scene(borderPane);
        //3.将 场景 设置到 舞台
        primaryStage.setScene(scene);
        //4.显示舞台
        primaryStage.show();

    }

    //返回一个自己制作的面板
    private PuzzlePane cretePane() {
        int rows = 0;
        int cols = 0;
        Image img = null;

        //获取菜单中选中的图片
        ObservableList<MenuItem> items = this.loadMenu.getItems();
        //遍历List
        for (int i = 0; i < items.size(); i++) {
            //取出每一个RadioMenuItem对象
            RadioMenuItem item = (RadioMenuItem) items.get(i);
            if (item.isSelected()) {
                //获取id属性的值作为“文件名”
                String fileName = item.getId();
                //System.out.println("当前选中的菜单的文件名为:"+fileName+".jpg");
                //创建一个图片对象:Image    ,,通过类加载器,然后在src下的文件夹下找指定的文件,当成一个输入流,然后把这个输入流当做一个参数传给Image的构造方法
                img = new Image(this.getClass().getClassLoader().getResourceAsStream("images/" + fileName + ".jpg"));
               /* System.out.println("图片的宽度" + img.getWidth());
                System.out.println("图片的高度" + img.getHeight());*/

                //计算可以切出多少块:每块的宽,高:100像素
                rows = (int) (img.getWidth() / 100); //横线有多少块
                cols = (int) (img.getHeight() / 100);//纵向有多少块
                break;
            }

        }
        //带方格的面板
        puzzlePane = new PuzzlePane(rows, cols);
        //生成小图块
        for (int i = 0; i < cols; i++) {
            for (int j = 0; j < rows; j++) {
                Piece piece = new Piece(img,
                        Piece.SIZE * j, Piece.SIZE * i,
                        i > 0, j > 0, i < cols - 1, j < rows - 1,
                        puzzlePane.getWidth(), puzzlePane.getHeight());
                //将图块添加到面板
                puzzlePane.getChildren().add(piece);
                //将图块添加到集合
                pieceList.add(piece);
            }
        }

        return puzzlePane;
    }

    private MenuBar createMenu() {
        //1.创建一个主菜单栏
        MenuBar menuBar = new MenuBar();
        //2.创建一个主菜单
        Menu mainMenu = new Menu("菜单");
        //3.创建一个加载图片的菜单Menu
        loadMenu = new Menu("加载图片");
        //4.创建一个“分割线”对象
        SeparatorMenuItem separatorMenuItem = new SeparatorMenuItem();
        //5.“开始”菜单---MenuItem
        MenuItem startItem = new MenuItem("开始");
        startItem.setOnAction(e -> {
            //1.判断动画是否正在播放
            if (timeline != null) {
                timeline.stop();//停止动画
            }
            //2.创建一个新的动画对象
            timeline = new Timeline();
            //3.为每个图片生成新的(x,y)坐标
            for (Piece piece : pieceList) {
                //设置图片为活动的
                piece.setActive();
                double x = Math.random() * (puzzlePane.getWidth() - piece.SIZE + 30f) - 15f - piece.getX();
                double y = Math.random() * (puzzlePane.getHeight() - piece.SIZE + 30f) - 15f - piece.getY();
                if (y<10){
                    y=10;
                }
                //添加到时间轴
                timeline.getKeyFrames().add(new KeyFrame(
                        Duration.seconds(1),new KeyValue(piece.translateXProperty(),x),
                        new KeyValue(piece.translateYProperty(),y)
                ));
            }
            //启动动画
            timeline.playFromStart();
        });
        //6."还原"菜单 ---MenuItem
        MenuItem reseItem = new MenuItem("还原");
        reseItem.setOnAction(e -> {
            if (timeline!=null){
                timeline.stop();
            }
            timeline = new Timeline();
            for (Piece p :pieceList) { // Duration.seconds(1)  1s
                //设置活动失效
                p.setInactive();
                timeline.getKeyFrames().add(new KeyFrame(
                        Duration.seconds(1),
                        new KeyValue(p.translateXProperty(),0),
                        new KeyValue(p.translateYProperty(),0)
                ));
            }
            //启动
            timeline.playFromStart();
        });

        /*************加载图片下的几个图片菜单 添加到组中,,组内单选*********/
        //1.创建一个:菜单组对象
        ToggleGroup toggleGroup = new ToggleGroup();
        //2.创建四个图片菜单
        RadioMenuItem item1 = new RadioMenuItem("第一关");//显示的文字
        item1.setToggleGroup(toggleGroup); //设置组
        item1.setId("dog1");//将文件名作为ID属性,后期获取id属性,就可以获取文件名了
        item1.setSelected(true);  //默认选中

        //添加事件,,,,,鼠标事件
        item1.setOnAction(e -> {
            toAction();
        });

        RadioMenuItem item2 = new RadioMenuItem("第二关");//显示的文字
        item2.setToggleGroup(toggleGroup); //设置组
        item2.setId("dog2");//将文件名作为ID属性,后期获取id属性,就可以获取文件名了
        //添加事件,,,,,鼠标事件
        item2.setOnAction(e -> {
            toAction();
        });

        RadioMenuItem item3 = new RadioMenuItem("第三关");//显示的文字
        item3.setToggleGroup(toggleGroup); //设置组
        item3.setId("dog3");//将文件名作为ID属性,后期获取id属性,就可以获取文件名了
        //添加事件,,,,,鼠标事件
        item3.setOnAction(e -> {
            toAction();
        });

        RadioMenuItem item4 = new RadioMenuItem("第四关");//显示的文字
        item4.setToggleGroup(toggleGroup); //设置组
        item4.setId("dog4");//将文件名作为ID属性,后期获取id属性,就可以获取文件名了
        //添加事件,,,,,鼠标事件
        item4.setOnAction(e -> {
            toAction();
        });


        //将这四个图片添加到:loadMenu中
        loadMenu.getItems().addAll(item1, item2, item3, item4);
        //添加到主菜单中
        mainMenu.getItems().addAll(loadMenu, separatorMenuItem, startItem, reseItem);

        menuBar.getMenus().add(mainMenu);
        return menuBar;

    }

    private void toAction() {
        //1.根据选择的图片,创建一个新的面板
        PuzzlePane puzzlePane = cretePane();
        //2.将面板设置到场景中
        borderPane.setCenter(puzzlePane);
        //3.设置主舞台的宽度和高度
        primaryStage.setWidth(puzzlePane.getPrefWidth() + 17);
        primaryStage.setHeight(puzzlePane.getPrefHeight() + 67);
    }

    public static void main(String[] args) {
        //JavaFx标准启动方法
        launch(args); // 是Application内部的启动方法,自动调用start()

    }
}

  1. 自己制作的面板类PuzzlePane
package com.guo.puzzle;

import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;

/**
 * 自己制作的面板
 */
public class PuzzlePane extends Pane {

    public PuzzlePane(int rows, int cols) {
        //根据rows和cols 计算此面板的宽度和高度
        this.setPrefWidth(rows*100);   //初始的宽度
        this.setPrefHeight(cols*100);  //初始的高度
        this.autosize(); //自动的计算宽度

        //2.设置样式---四边的阴影--使用Fx的css
        this.setStyle("-fx-background-color: #fff;-fx-border-color: #fff;" +
                "-fx-effect: innershadow(three-pass-box,rgba(0,0,0,0.8),15,0.0,0,4)");
        //3.画出方格
        //1.创建一个路径
        Path path = new Path();
        //2.设置线的颜色
        path.setStroke(Color.rgb(255,70,70));
        //3.将path设置到面板中
        this.getChildren().add(path);
        //4.画横线-----根据cols
        for (int i = 0; i < cols-1; i++) {
            path.getElements().addAll(
                    new MoveTo(5,(i+1)*100),   //设置开始时(x,y)的坐标
                    new LineTo(this.getPrefWidth()-5,(i+1)*100)  //设置结束时的坐标
            );
        }
        //5.画竖线-----根据rows
        for (int i = 0; i < rows-1; i++) {
            path.getElements().addAll(
                    new MoveTo((i+1)*100,5),
                    new LineTo((i+1)*100,this.getPrefHeight()-5)
            );

        }
    }
}

  1. 图片按照一定的形状进行曲线切割成小图块,图块类 Piece
package com.guo.puzzle;

import javafx.geometry.Point2D;
import javafx.scene.Parent;
import javafx.scene.effect.DropShadow;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Ellipse;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;


/**
 * 小图块的效果
 */
public class Piece extends Parent {  //继承一个容器
    //成员属性
    public static final int SIZE = 100; // 代表图片的宽高
    //图块在面板中的(x,y)坐标
    private double x;
    private double y;
    //四边是否需要处理
    private boolean hasTopTab;
    private boolean hasLeftTab;
    private boolean hasBottomTab;
    private boolean hasRightTab;

    //拖拽图片前的(x,y)坐标
    private double startDragX;
    private double startDragY;
    private Point2D dragAnchor;

    //构造方法
    public Piece(Image image,
                 double x, double y,
                 boolean hasTopTab, boolean hasLeftTab, boolean hasBottomTab, boolean hasRightTab,
                 double puzzlePanewidth, double puzzlePaneHeight) {
        this.x = x;
        this.y = y;
        this.hasTopTab = hasTopTab;
        this.hasLeftTab = hasLeftTab;
        this.hasBottomTab = hasBottomTab;
        this.hasRightTab = hasRightTab;
        //获取图形
        Shape shape = drawPiece();
        shape.setFill(Color.WHITE);  //填充颜色为白色
        shape.setStroke(null); //设置无边框

        //获取边框形状
        Shape pieceStroke = drawPiece();
        pieceStroke.setFill(null); //清空内部的颜色
        pieceStroke.setStroke(Color.BLACK);

        //创建图片
        ImageView imageView = new ImageView(image);
        //按照我们的形状切割图片
        imageView.setClip(shape);
        //将图片,和边框添加到当前容器
        this.getChildren().addAll(imageView, pieceStroke);

        //为此图片块添加三个事件
        //1.鼠标按下
        this.setOnMousePressed(e -> {
            toFront();
            //记录当前图片的x,y位置
            startDragX = this.getTranslateX();
            startDragY = this.getTranslateY();
            dragAnchor = new Point2D(e.getSceneX(), e.getSceneY());
        });
        //2.鼠标拖拽
        this.setOnMouseDragged(e -> {
            //1.计算新的移动的位置
            double newX = startDragX + e.getSceneX() - dragAnchor.getX();
            double newY = startDragY + e.getSceneY() - dragAnchor.getY();
            //2.移动的最小位置
            double minX = -45f - x;
            double miny = -30f - y;
            //3.移动的最大位置
            double maxX = puzzlePanewidth - Piece.SIZE + 50f - x;
            double maxY = puzzlePaneHeight - Piece.SIZE + 70f - y;

            if ((newX > minX) && newX < maxX && newY > miny && newY < maxY){
                this.setTranslateX(newX);
                this.setTranslateY(newY);
            }
        });
        //3.鼠标抬起
        this.setOnMouseReleased(e -> {
            if (getTranslateX()<10&&getTranslateX()>-10&&
            getTranslateY()<10&&getTranslateY()>-10){
                //吸附
                setTranslateX(0);
                setTranslateX(0);
                //设置失效
                this.setInactive();
            }

        });


    }

    //绘制最终的图片
    private Shape drawPiece() {
        //1.绘制一个大矩形
        Rectangle bigRct = new Rectangle(0, 0, Piece.SIZE, Piece.SIZE);
        //2.创建一个新形状
        Shape shape = bigRct;
        //根据上下左右的标记,绘制形状
        if (hasRightTab) { //右侧
            //大矩形 + 右侧的凸起
            shape = Shape.union(shape, this.drawPieceBorder(Piece.SIZE, (Piece.SIZE - 25f) / 2, 11.5f, 25f,
                    Piece.SIZE + 11.5f + 10f - 2f, Piece.SIZE / 2f, 10f, 17.5f,
                    Piece.SIZE + 6.25f, (Piece.SIZE - 25f) / 2 - 1.5f, 6.25f,
                    Piece.SIZE + 6.25f, (Piece.SIZE - 25f) / 2 + 25f + 1.5f, 6.25f));
        }
        if (hasBottomTab) { //下测凸起
            shape = Shape.union(shape, drawPieceBorder(
                    (Piece.SIZE - 25f) / 2f, Piece.SIZE, 25f, 11.5f,
                    Piece.SIZE / 2f, Piece.SIZE + 11.5f + 10f - 2f, 17.5f, 10f,
                    (Piece.SIZE - 25f) / 2f - 1.5f, Piece.SIZE + 6.25f, 6.25f,
                    (Piece.SIZE - 25f) / 2f + 25f + 1.5f, Piece.SIZE + 6.25f, 6.25f

            ));

        }
        if (hasLeftTab) { //左侧凹陷
            shape = Shape.subtract(shape, drawPieceBorder(
                    0, (Piece.SIZE - 25f) / 2f, 11.5f, 25f,
                    11.5f + 10f - 2f, Piece.SIZE / 2f, 10f, 17.5f,
                    6.25f, (Piece.SIZE - 25f) / 2f - 1.5f, 6.25f,
                    6.25f, (Piece.SIZE - 25f) / 2f + 25f + 1.5f, 6.25f

            ));
        }
        if (hasTopTab) {  //上侧凹陷
            shape = Shape.subtract(shape, drawPieceBorder(
                    (Piece.SIZE - 25f) / 2, 0, 25f, 11.5f,
                    Piece.SIZE / 2f, 11.5f + 10f - 2f, 17.5f, 10f,
                    (Piece.SIZE - 25f) / 2f - 1.5f, 6.25f, 6.25f,
                    (Piece.SIZE - 25f) / 2f + 25f + 1.5f, 6.25f, 6.25f

            ));
        }
        //设置图片块的(x,y)坐标
        shape.setLayoutX(x);
        shape.setLayoutY(y);
        //将最终的小图块返回
        return shape;                   
    }

    //内部的方法,绘制四边的凸起/凹陷形状
    private Shape drawPieceBorder(double rectangleX, double rectangleY,
                                  double rectangleWidth, double rectangleHeight,
                                  double ellipseCenterX, double ellipseCenterY,
                                  double ellipseRadiusX, double ellipseRadiusY,
                                  double circle1CenterX, double circle1CenterY, double circle1Radius,
                                  double circle2CenterX, double circle2CenterY, double circle2Radius
    ) {
        //1.画出小矩形
        Rectangle rct = new Rectangle(rectangleX, rectangleY, rectangleWidth, rectangleHeight);
        //2.画出椭圆
        Ellipse ellipse = new Ellipse(ellipseCenterX, ellipseCenterY, ellipseRadiusX, ellipseRadiusY);
        //合并
        Shape shape = Shape.union(rct, ellipse);
        //3.第一个小圆
        Circle c1 = new Circle(circle1CenterX, circle1CenterY, circle1Radius);
        shape = Shape.subtract(shape, c1);
        //4.第二个小圆
        Circle c2 = new Circle(circle2CenterX, circle2CenterY, circle2Radius);
        shape = Shape.subtract(shape, c2);

        return shape;
    }

    public double getX() {
        return x;
    }

    public double getY() {
        return y;
    }

    //当前图片漂浮起来后,设置为活动的
    public void setActive() {
        //1.设置不失效
        this.setDisable(false);
        //2.设置阴影
        this.setEffect(new DropShadow());
        //3.设置图片在组件的前面
        this.toFront();
    }

    //当图片还原时候,设置失效
    public void setInactive() {
        //1.设置主键失效
        this.setDisable(true);
        //2.取消阴影效果
        this.setEffect(null);
    }

}

java拼图小游戏_第4张图片

你可能感兴趣的:(课程设计,java,开发语言,后端)