JAVA一分钟游戏编程——窗口显示内容(02)

1. 目标

一分钟在窗口显示内容

2. 面板

JFrame类似一个窗口,但是不具备绘制功能。

我们想在空白的窗口上显示内容,比如背景图、游戏对象、UI控件等,则需要用到javax.swing中的面板类JPanel。

JFrame窗口和JPanel面板的关系就类似画板和画布的关系,我们只能在画布上绘图。

3. 面板分层

我们解了面板是绘图的容器,但游戏画面中包含许多的元素,比如背景、游戏对象、UI控件等,我们把这些元素都绘制在一个面板中是否合适呢?

答案是否定的,如果只用一个面板绘制这些元素,会带来一系列问题:

  • 逻辑混乱:背景绘制、角色碰撞、按钮点击事件都会混杂在JPanel的paintComponent() 方法和各类监听器中,随着游戏复杂度提升,代码会显得越来越臃肿;

  • 渲染效率低下:例如,在游戏中角色每帧移动,即使背景和UI完全不变,面板也会重新绘制所有元素,造成大量不必要的重复计算;

  • 元素显示冲突:需要严格控制绘制顺序,如果绘制顺序出错,先绘制UI,再绘制角色,再绘制背景,导致UI被角色遮挡或游戏对象被背景覆盖。

为了解决以上问题,我们将游戏面板进行分层显示,至少包含三个层级面板(背景层、游戏层、UI层),这些层级面板之间的绘制和事件监听各自为营、互不干扰:

4. 代码实现

4.1 背景层面板BgPanel

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

/**
 * 背景面板类,继承自JPanel
 * 专门用于显示游戏或应用程序的背景图片,支持背景图自适应面板大小
 */
public class BgPanel extends JPanel {

    // 用于存储背景图片的缓冲图像对象
    // BufferedImage支持更灵活的图像操作,适合需要频繁绘制的场景
    private BufferedImage bgImage;

    /**
     * 构造方法:初始化背景面板
     */
    public BgPanel() {
        // 设置布局管理器为BorderLayout
        // BorderLayout会让添加的组件自动填充整个面板,适合作为背景面板
        setLayout(new BorderLayout());

        // 加载背景图片
        try {
            // 从指定路径读取图片文件
            // 这里的路径是相对路径,表示项目根目录下的assets/images/bg.png
            bgImage = ImageIO.read(new File("assets/images/bg.png"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 重写面板的绘图方法
     * 该方法会在面板需要刷新时自动调用(如窗口大小改变、调用repaint()时)
     * @param g 绘图上下文对象,用于执行绘图操作
     */
    @Override
    protected void paintComponent(Graphics g) {
        // 调用父类的paintComponent方法,确保面板正常刷新(如清除原有绘制内容)
        super.paintComponent(g);

        // 绘制背景图片
        // bgImage:要绘制的图片对象
        // 0, 0:图片在面板中的起始坐标(左上角)
        // getWidth(), getHeight():图片要拉伸到的宽度和高度(即面板的大小)
        // this:当前面板作为图像观察者,监听图片加载状态
        if (bgImage != null) { // 确保图片加载成功后再绘制,避免空指针异常
            g.drawImage(bgImage, 0, 0, getWidth(), getHeight(), this);
        }
    }
}

4.2 游戏层面板GamePanel

import javax.swing.*;
import java.awt.*;

/**
 * 游戏面板类,继承自JPanel
 * 用于绘制和管理游戏中的动态元素(如角色、道具等)
 * 作为中间层面板,通常叠加在背景面板之上
 */
public class GamePanel extends JPanel {

    /**
     * 构造方法:初始化游戏面板
     */
    public GamePanel() {
        // 设置布局管理器为BorderLayout
        // 便于后续添加子组件时按方位(东、南、西、北、中)布局
        setLayout(new BorderLayout());

        // 设置面板为透明
        // 关键作用:避免遮挡下层面板(如背景面板),确保背景可见
        setOpaque(false);
    }

    /**
     * 重写绘图方法,自定义面板内容绘制
     * 该方法会在面板刷新时自动调用,用于绘制游戏元素
     *
     * @param g 绘图上下文对象,提供绘图相关的方法
     */
    @Override
    protected void paintComponent(Graphics g) {
        // 调用父类的paintComponent方法
        // 作用:1. 清除面板原有内容,避免画面残留;2. 执行面板默认的绘制逻辑
        super.paintComponent(g);

        // 设置当前绘图颜色为深灰色
        g.setColor(Color.DARK_GRAY);

        // 在面板左上角绘制一个60x60像素的矩形(表示游戏角色对象)
        // 参数说明:x=100, y=100(距离左上角坐标的相对位置);width=60, height=60(矩形宽高)
        g.fillRect(100, 100, 60, 60);
    }

}
    

4.3 UI层面板UIPanel

import javax.swing.*;
import java.awt.*;

/**
 * UI面板类,继承自JPanel
 * 用于展示游戏中的用户界面元素(如分数显示、控制按钮等)
 * 作为顶层面板,通常叠加在游戏面板之上,提供用户交互功能
 */
public class UIPanel extends JPanel {

    // 分数显示标签,用于实时展示游戏分数
    private JLabel scoreLabel;
    // 暂停按钮,用于控制游戏暂停/继续
    private JButton pauseBtn;

    /**
     * 构造方法:初始化UI面板及其中的控件
     */
    public UIPanel() {
        // 设置布局管理器为FlowLayout,左对齐
        // 参数说明:FlowLayout.LEFT(左对齐)、水平间距20px、垂直间距10px
        // 适合排列按钮、标签等小型UI元素
        setLayout(new FlowLayout(FlowLayout.LEFT, 20, 10));

        // 设置面板为透明
        // 确保不会遮挡下层的游戏面板和背景面板
        setOpaque(false);

        // 初始化分数标签
        scoreLabel = new JLabel("Score: 0");
        // 设置字体:Arial字体、粗体、20号大小
        scoreLabel.setFont(new Font("Arial", Font.BOLD, 20));
        // 设置文字颜色为白色(适合深色背景)
        scoreLabel.setForeground(Color.WHITE);
        // 将标签添加到面板中
        add(scoreLabel);

        // 初始化暂停按钮
        pauseBtn = new JButton("Pause");
        // 为按钮添加点击事件监听器
        pauseBtn.addActionListener(e -> {
            // 点击按钮时输出日志(实际开发中可替换为暂停游戏逻辑)
            System.out.println("Pause button clicked");
        });
        // 将按钮添加到面板中
        add(pauseBtn);
    }
}
    

4.4 GameWindow构造调整

public GameWindow(String title, int width, int height) {
	// 保存窗口标题、宽度和高度到成员变量
	this.title = title;
	this.width = width;
	this.height = height;

	// 初始化主窗口JFrame
	frame = new JFrame(title);
	// 设置窗口关闭操作:关闭窗口时退出程序
	frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

	// 创建层级面板JLayeredPane,用于管理不同层级的面板
	// 层级面板允许组件按层级叠加显示,解决元素遮挡问题
	JLayeredPane layeredPane = new JLayeredPane();
	// 设置层级面板的首选大小为窗口大小
	layeredPane.setPreferredSize(new Dimension(width, height));

	// 1. 添加背景层(最底层)
	BgPanel bgPanel = new BgPanel();
	// 设置背景面板大小与窗口一致,位置从左上角(0,0)开始
	bgPanel.setBounds(0, 0, width, height);
	// 将背景面板添加到层级面板的默认层(最低层级)
	layeredPane.add(bgPanel, JLayeredPane.DEFAULT_LAYER);

	// 2. 添加游戏元素层(中间层)
	GamePanel gamePanel = new GamePanel();
	// 设置游戏面板大小与窗口一致,覆盖在背景层之上
	gamePanel.setBounds(0, 0, width, height);
	// 将游戏面板添加到调色板层(层级高于默认层)
	layeredPane.add(gamePanel, JLayeredPane.PALETTE_LAYER);

	// 3. 添加UI层(最顶层)
	UIPanel uiPanel = new UIPanel();
	// 设置UI面板大小与窗口一致,覆盖在所有层之上
	uiPanel.setBounds(0, 0, width, height);
	// 将UI面板添加到模态层(层级高于调色板层,确保UI控件优先显示)
	layeredPane.add(uiPanel, JLayeredPane.MODAL_LAYER);

	// 将层级面板添加到主窗口
	frame.add(layeredPane);
	// 调整窗口大小以适应层级面板的首选大小
	frame.pack();

	// 设置窗口在屏幕中居中显示
	frame.setLocationRelativeTo(null);
}

5. 代码说明

以上我们创建了三个面板类BgPanel、GamePanel、UIPanel,然后由底到顶分别置于不同的层级(辅助类为JLayeredPane,用于管理不用层级的面板)。

另外,我们移除了frame.setSize()方法,使用JLayeredPane的setPreferredSize()方法,最后调用frame.pack()方法调整窗口以适应子元素的大小。

如果你尚不清楚面板的布局,可以温习一下swing的基本知识,但不必过多深入,掌握常用组件的使用即可。

为什么不能继续使用frame.setSize()?

因为setSize方法设置的是整个窗口(包括标题栏、边框)的大小,减去这部分后,内部绘制区域将小于我们预期的800 x 600,这将导致游戏元素显示不完全。

重新运行程序,呈现效果如下:

如果你做到了这一步,那么恭喜你,你已经很🐂啦!

源码链接:GitCode - 全球开发者的开源社区,开源代码托管平台

6. 思考:如何让游戏对象移动起来?

目前,我们已经成功在窗口上显示内容了,但这些内容都是静态的,依然无趣,下一节我们让游戏对象动起来试试。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值