游戏窗体应用开发

为了带着小伙伴们很好的实践基于Java语言的设计模式,咱们将开发一个仿90版的红白机坦克大战小游戏。该游戏基于Java Swing组件来开发,在开发整个游戏过程中,作者会带着小伙伴一步步实现游戏中的各种功能,一步步对代码进行重构,以消除代码中的“坏味道”;同时为了更好的对游戏的功能进行扩展、对代码更好的维护并增加可读性,咱们会适当的使用设计模式。通过不断的重构和对设计模式的实践,让大伙儿真正理解设计模式的使用场景,对大家工作中编码、学习框架源码以及面试都会有一定的帮助。

最简单的Swing窗体应用程序

再回到本节的主题,咱们用基本的Java Swing组件来快速开发一个main方法启动的包含游戏绘图板的最简单的窗体应用。

image.png

实现该窗体应用的所有代码都在一个GameMain类中,代码如下:

package com.pf.java.tankbattle;





​







import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
​
public class GameMain extends JFrame {
​

    public GameMain() {
        // 设置窗口的标题
        setTitle("坦克大战1.0");
​

        // 设置窗口背景色
        setBackground(Color.BLACK);
​
        // 设置窗口不可拖动来改变大小
        setResizable(false);
​
        // 设置程序启动后,窗口弹出可见,同时开启后台线程来对图形化界面渲染和事件交互的监听
        setVisible(true);
​
        // 创建一个绘图板组件,用于后期游戏画面的绘制
        JPanel panel = new JPanel();
        // 将绘图板组件添加到窗口对象的内容绘图板中
        getContentPane().add(panel);
​
        // -------- 设置绘图板组件的外观 --------
        // 设置绘图板亮灰色背景
        panel.setBackground(Color.lightGray);
        // 设置绘图板的宽高(像素单位)
        panel.setPreferredSize(new Dimension(320, 240));
​
        // 让整个窗口包住游戏绘图板
        pack();
​
        // 让窗口显示在屏幕正中间
        setLocationRelativeTo(null);
​
        // 给窗口添加一个监听器,监听窗口关闭时手动退出主线程
        addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                System.out.println("程序结束");
                // 手动退出主线程,参数值0代表正常退出,而1代表异常情况退出
                System.exit(0);
            }
        });
    }
​
    public static void main(String[] args) {
        // 创建窗口对象,并进行窗口的初始化
        new GameMain();
    }
​
}

关于代码的说明

完整的代码后期会维护到Github上。教程中会将实现功能所涉及的关键代码都贴出来,确保大伙儿能够在阅读本套教程中,边看边练,跟上作者对游戏功能开发、代码迭代的节奏和重构的思路。为此,作者会尽量为每一行代码增加注释。游戏涉及的素材,在源码发布出来之前,作者会分享在网盘中供大伙儿下载。

以上的代码相信大伙儿都看得明白,窗体、绘图板的设置逻辑都在GameMain类的无参构造中。运行程序后,游戏窗体默认居中弹出,窗口大小由JPanel绘图板对象设置的宽高决定,并且不能改变大小,点击窗口关闭按钮后程序正常退出。

拆分组件类

接下来,咱们将对这一个类文件按照组件进行拆分,分成几个类文件。这也符合软件设计中的“单一职责”原则。拆分后的类文件:

image.png

游戏绘图板类MyPanel负责游戏画面的绘制,目前仅实现了外观设置:

package com.pf.java.tankbattle;





​







import ...





​






public class MyPanel extends JPanel {


​





    public MyPanel() {

        // -------- 设置绘图板组件的外观 --------
        // 设置绘图板亮灰色背景
        setBackground(Color.lightGray);
        // 设置绘图板的宽高(像素单位)
        setPreferredSize(new Dimension(320, 240));
    }
}

说明

为了减少篇幅,import语句后的内容都会被省略。同样,前面贴出来的代码中完全一样内容也会被省略掉,以...代替。

窗体类MyFrame用于窗体的外观设置、组件的添加、事件监听器的注册等等,这里有变动的地方则是,构造器接收外部传入的JPanel对象,添加到内容面板中:

package com.pf.java.tankbattle;





​







import ...





​






public class MyFrame extends JFrame {
​





    public MyFrame(JPanel panel) {
        ...
​

        // 将绘图板组件添加到窗口对象的内容面板中
        getContentPane().add(panel);
​

        ...
    }
}

游戏的主类GameMain简化为:

package com.pf.java.tankbattle;
​







public class GameMain {
​






    public static void main(String[] args) {
        // 创建窗体对象
        new MyFrame(new MyPanel());
    }
}

绘图板绘制功能

首先来认识下Java Swing窗体绘制的坐标系:

image.png

从图中可以看出,坐标点的数值是向右下方变大的,而所在的绘制组件的左上角作为坐标原点(0, 0),要绘制的内容,比如图形或图片,是以左上角的坐标作为起点的,比如上图中绘制的矩形,起点为左上角坐标(20, 20),宽度和高度都是50(像素)。

代码实现如下:

package com.pf.java.tankbattle;





​







import ...





​






public class MyPanel extends JPanel {


​





    public MyPanel() { ... }
​
    @Override
    protected void paintComponent(Graphics g) {
        // 必须要调用父类方法来完场组件的基本绘制,比如构造器中设置的背景色等等
        super.paintComponent(g);
        System.out.println("paintComponent...");
        // 绘制一个黄色的小方格子
        // 先设置画笔的颜色
        g.setColor(Color.yellow);
        // 填充一个矩形
        g.fillRect(20, 20, 50, 50);
    }
}

说明

这里重写Swing组件的paintComponent方法来实现自定义的绘制功能,但需要注意,必须先调用super.paintComponent(g)完成组件基本的外观的绘制。该方法提供了一个Graphics类型的画笔对象,我们可以通过调用其setColor(color)方法来“沾色”并调用其相关的api以完成绘制。

关于该绘制方法何时被调用,咱们打印了一行信息,运行程序会发现,在窗体第一次显示以及窗口最小化后再次出现时都会被调用,而且会被调用多次,在必要时都会进行重绘。

接下来绘制咱们的主题元素——坦克。先看下位于资源包下的素材文件:

image.png

打开tank.png,发现这张图特别长:

image.png

这里包含了90版红白机坦克大战中所有类型的坦克以及道具等素材,咱们从中扣取第一个位置的坦克,把它绘制到绘图板中。

首先定义一个用来加载静态图片的工具类:

package com.pf.java.tankbattle;
​







import ...





​






public class ResourceMgr {
​





    /** 代表坦克图片素材的图片对象 */
    public static BufferedImage tank;
    
    static {
        // 获取加载当前类的类加载器
        ClassLoader cl = ResourceMgr.class.getClassLoader();
        try {
            // 类文件和静态资源编译后位于相同的类路径下,因此可被加载class的类加载器直接获取到
            tank = ImageIO.read(cl.getResourceAsStream("images/tank.png"));
            System.out.println("游戏素材加载完毕...");
        } catch (IOException e) {
            e.printStackTrace();
            // 如果游戏素材加载失败,则程序异常退出
            System.exit(1);
        }
    }
}

注意

ResourceMgr类中的静态代码块会在第一次访问其静态成员变量时被执行。因此在程序启动后应该优先加载静态资源,再开始游戏,将加载后的图片对象绘制到游戏绘图板中。

package com.pf.java.tankbattle;





​







import ...





​






public class GameMain {
​





    public static void main(String[] args) {
        // 先加载静态资源,这里仅为了访问静态成员变量以执行静态代码块完成资源加载,声明的变量没有地方使用
        Image imageResource = ResourceMgr.tank;
        // 创建窗体对象
        ...
    }
​

}

是时候在绘图板上绘制我们的英雄——小黄坦克了

image.png

实现的代码如下:

package com.pf.java.tankbattle;





​







import ...





​






public class MyPanel extends JPanel {


​





    public MyPanel() {

        // 设置绘图板纯黑背景,让坦克显的更耀眼些
        setBackground(Color.BLACK);
        ...
    }
​

    @Override
    protected void paintComponent(Graphics g) {
        // 必须要调用父类方法来完场组件的基本绘制,比如构造器中设置的背景色等等
        super.paintComponent(g);
​
        // 先从左上角开始扣取宽高都为32像素的区域,然后绘制到绘图板坐标为(20, 20)的位置
        // drawImage最后一个观察者对象可设置为null,因为不涉及到图片转换操作,也无须通知
        g.drawImage(ResourceMgr.tank.getSubimage(0, 0, 32, 32), 20, 20, null);
    }
}

好了,这一小节主要带着小伙伴一起快速上手Java Swing应用程序,知道怎么开发一个不涉及按钮、工具栏以及复杂布局的简单的窗体应用,并能够在JPanel组件中绘制一些内容,这为我们接下来的学习做好了铺垫。下一小节,我们将学习如何定义坦克类并用多线程来操控玩家坦克,大家加油!

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYReq2Gc' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片