如何在Java中创建具有变化的纹理和颜色的交互式面板?

时间:2018-12-14 20:56:25

标签: java swing graphics awt 2d-games

我是新来的,但发布之前我做了一些研究。我的目标是使用几个有趣的想法创建一个简单的塔防游戏,并使用javax.swing和java.awt训练我的开发技能。据我所知,开发人员大多是懒惰的人,他们尽一切努力使生活更简单。

有一个带有网格的地图,为了加载地图,我的游戏使用布尔矩阵和加载方法在面板上定位地形。我认为这将是非常简单的解决方案。因为矩阵是12 x 12,所以我想用其他应用程序来创建它,而不是输入144个数字的行。

这里有一个想法,首先创建一个地图编辑器应用程序,然后在其中创建关卡地图。当我拥有这样的工具时,我可以直观地制作该地图,然后将其布尔矩阵保存到文件中,以后可以通过加载方法读取该文件并在游戏中重新创建。下一步是制作图形以及面板,以对用户的动作做出适当的反应。左侧是一个带有按钮的面板-用户单击其中一个按钮后,currentColor字段将更改。

此字段由实现actionListener并对其构造函数中声明的面板进行颜色更改的方法使用。我想在单击某些面板时更改其颜色。我之所以使用颜色,是因为它现在更易于使用,以后我想用纹理替换颜色-显然,我知道我必须使用paintComponent方法,但是我认为这也适用于此,对吗?如果当我将光标移到面板边框上时面板边框的颜色发生变化,而当鼠标位于其他位置时,面板边框的颜色又恢复为正常,那也很好。

这里的意思是我在使面板互动方面遇到一些麻烦。第一个问题是在for循环中创建面板,这使得在鼠标悬停在某个面板上时很难引用该面板。另一个是我想在单击该面板后更改该面板的外观。

据我所知,MouseListeners应该可以完成这项工作,但是如何实际编写它才能在屏幕上产生效果呢?我发现了一些有关此的帖子,但对我来说却行不通。这是链接:highlighting panels in java

我的代码:

import javax.swing.*;
import javax.swing.border.LineBorder;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

public class Editor extends JFrame
{
    private JButton towers = new JButton(new ImageIcon("pu.gif"));
    private JButton road = new JButton(new ImageIcon("pu.gif"));
    private JButton start = new JButton(new ImageIcon("pu.gif"));
    private JButton finish = new JButton(new ImageIcon("pu.gif"));
    private String mapTitle = "testmap";
    private Color currentColor;
    private int width = Toolkit.getDefaultToolkit().getScreenSize().width;
    private int height = Toolkit.getDefaultToolkit().getScreenSize().height;
    private String currentMapType = "Standard";
    private static final int currentHeight = 12;
    private static final int currentWidth = 12;
    private JPanel[][] currentMapPanel;
    private int[][] currentMapField;

    //Toolbar - a panel with buttons

    private JPanel panel = new JPanel(new GridLayout(10,3));

    //Container for map - a panel with map

    private Dimension containerSize = new Dimension(height, height);
    static JPanel container = new JPanel(new GridLayout(currentHeight, currentWidth), true);

    //Separator

    private JSplitPane separator = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, panel, container);

    public Editor()
    {
        initComponents();
    }

    public void initComponents()
    {
        this.setTitle(mapTitle + ".map" + " - " + "Game Map Editor");
        this.setSize(800, 600);

        int frameWidth = this.getSize().width;
        int frameHeight = this.getSize().height;
        this.setLocation((width - frameWidth) / 2, (height - frameHeight) / 2);

        this.setIconImage(Toolkit.getDefaultToolkit().getImage("pu.gif"));

        towers.addActionListener(e -> {
            currentColor = Color.CYAN;
            System.out.println(currentColor);
        });
        road.addActionListener(e -> {
            currentColor = Color.GRAY;
            System.out.println(currentColor);
        });
        start.addActionListener(e -> {
            currentColor = Color.LIGHT_GRAY;
            System.out.println(currentColor);
        });
        finish.addActionListener(e -> {
            currentColor = Color.BLACK;
            System.out.println(currentColor);
        });

        new Map(currentMapType, currentWidth, currentHeight, false);

        panel.add(towers);
        panel.add(road);
        panel.add(start);
        panel.add(finish);

        this.getContentPane().add(separator);

        this.setDefaultCloseOperation(EXIT_ON_CLOSE);

    }

    /**
     * Class that allows to load the graphic map and to view it in JFrame
     */
    public class Map
    {
        public Map(String mapType, int rows, int columns, boolean load)
        {
            if (!load)
            {
                currentMapPanel = mapPanel(rows, columns);
                currentMapField = new MapGenerator().mapFieldEmpty(rows, columns);
                mapLoader(currentMapField, currentMapPanel);
            }
            else
            {
                currentMapPanel = mapPanel(rows, columns);
                currentMapField = new MapGenerator().mapFieldGenerator(rows, columns);
                mapLoader(currentMapField, currentMapPanel);
            }
        }

        private JPanel[][] mapPanel(int rows, int columns)
        {
            JPanel[][] mapPanel = new JPanel[rows][columns];

            for (int i = 0; i < rows - 1; i++)
            {
                for (int j = 0; j < columns - 1; j++)
                {
                    mapPanel[i][j] = new JPanel(true);
                    mapPanel[i][j].setPreferredSize(new Dimension(height/12, height/12));
                    mapPanel[i][j].setBorder(new LineBorder(Color.BLACK));
                    mapPanel[i][j].addMouseListener(new MouseAdapter() {
                        @Override
                        public void mouseEntered(MouseEvent e) {
                            super.mouseEntered(e);
                            JPanel parent = (JPanel) e.getSource();
                            new colorListener(parent, Color.LIGHT_GRAY);
                            parent.revalidate();
                        }
                        @Override
                        public void mouseExited(MouseEvent e)
                        {
                            super.mouseExited(e);
                            JPanel parent = (JPanel) e.getSource();
                            new colorListener(parent, Color.GREEN);
                            parent.revalidate();
                        }
                        @Override
                        public void mouseClicked(MouseEvent e)
                        {
                            super.mouseClicked(e);
                            JPanel parent = (JPanel) e.getSource();
                            new colorListener(parent, currentColor);
                            parent.revalidate();
                        }
                    });
                }
            }
            return mapPanel;
        }

        private void mapLoader(int[][] mapField, JPanel[][] mapPanel)
        {
            for (int i = 0; i < mapField.length - 1; i++)
            {
                for (int j = 0; j < mapField.length - 1; j++)
                {
                    if (mapField[i][j] == 0)
                    {
                        mapPanel[i][j].setBackground(Color.GREEN);
                        container.add(mapPanel[i][j]);
                    }
                    else if (mapField[i][j] == 1)
                    {
                        mapPanel[i][j].setBackground(Color.GRAY);
                        container.add(mapPanel[i][j]);
                    }
                    else if (mapField[i][j] == 2)
                    {
                        mapPanel[i][j].setBackground(Color.LIGHT_GRAY);
                        container.add(mapPanel[i][j]);
                    }
                    else if (mapField[i][j] == 3)
                    {
                        mapPanel[i][j].setBackground(Color.BLACK);
                        container.add(mapPanel[i][j]);
                    }
                    else
                    {
                        System.out.println("An error occurred...");
                    }
                }
            }
        }
        private JPanel mapContainer(int rows, int columns)
        {
            container = new JPanel();
            container.setLayout(createLayout(rows, columns));
            container.setPreferredSize(containerSize);
            container.setBounds(height/4, height/4, containerSize.width, containerSize.height);
            return container;
        }
        private GridLayout createLayout(int rows, int columns){
            GridLayout layout = new GridLayout(rows, columns);
            return layout;
        }
    }

    private class colorListener implements ActionListener
    {
        public colorListener(JPanel p, Color c)
        {
            this.panel = p;
            this.color = c;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            panel.setBackground(color);
        }

        JPanel panel;
        Color color;
    }

    public static void main(String[] args) {
        new Editor().setVisible(true);
    }
}

1 个答案:

答案 0 :(得分:1)

问题很广泛,答案也很复杂。

本质上,您希望对“职责分离”和“解耦代码”等概念进行一些研究。

这个想法是您分解功能需求,以便您的对象执行一项专门的工作。您还可以“解耦”代码,以使更改一个部分的实现不会对程序的其他部分产生不利影响。这通常是通过使用接口来实现的。

您还将要研究“模型-视图-控制器”的概念,其中“数据”或“状态”是在一个或多个类中建模的,但完全独立于UI。然后,UI可以自由使用它认为合适的方式“渲染”模型。

通过这种方式,“视图”(与控制器交互)可以更改模型的状态(或对状态变化做出反应),从而更易于管理(不认真,确实如此)

代码检查...

这个...

static JPanel container = new JPanel(new GridLayout(currentHeight, currentWidth), true);

是危险的,也是一个坏主意。它使封装的概念无效,并允许任何人随时创建container的新实例,而无需通知,这将使它与程序以前使用的内容断开连接。实际上,您实际上是这样做的。

static不是您的朋友。正确使用它是有用的,但是以这种方式使用,这是一个坏主意,应避免使用。

相反,您应该支持“依赖注入”,将任何一个对象所依赖的“元素”传递给它。

我会避免...

this.setSize(800, 600);

int frameWidth = this.getSize().width;
int frameHeight = this.getSize().height;
this.setLocation((width - frameWidth) / 2, (height - frameHeight) / 2);

Windows是复杂的组件,其中还包含包裹内容的窗口装饰。这意味着内容的可用空间为window size - window decorations。代替。您应该依靠布局管理器API提供适当的大小提示和pack框架。

在大多数现代OS上,您拥有“其他”系统元素,这又减少了屏幕上的可用空间(坞站,任务栏,其他时髦的东西)。相反,您可以使用setLocationRelativeTo(null)使窗口在屏幕上更可靠地居中。

您应该使用Window#setIconImages(List)而不是setIconImage,它允许您传递许多图像,API可以使用这些图像来表示需要不同分辨率图像的不同位置的应用程序。

不确定什么...

new Map(currentMapType, currentWidth, currentHeight, false);

但这并没有真正的帮助。

如果您发现自己只是创建类的实例而没有实际维护对它的引用,那么这可能是不良设计的一个好兆头。

您的Map类提出了一些不容易回答的问题。这让我担心Map类正在修改父类的状态,并大喊“依赖注入”。

这个...

mapPanel[i][j].setPreferredSize(new Dimension(height / 12, height / 12));
最好避免

。您应该更喜欢覆盖getPreferredSize,它应该简单地返回一个“所需”的大小,然后GridLayout之类的东西就可以使用它来更有效地布局组件。

这随后导致“责任分离”。本节建议您应该有一个“ tile”类,该类将进行自我管理并负责模型中的单个元素。

鼠标事件处理方面有很多问题……

@Override
public void mouseEntered(MouseEvent e) {
    super.mouseEntered(e);
    JPanel parent = (JPanel) e.getSource();
    new colorListener(parent, Color.LIGHT_GRAY);
    parent.revalidate();
}

在这些方法的工作中,您不应该调用super.mouseXxx(e)来调用委托MouseListener,所以,就在那里搞乱。

您可以更轻松地使用e.getComponent()获取生成事件的组件的引用,但是如果面板是一个独立的工作单元(即Tile)和MouseListener匿名或内部类,您就可以完全放弃演员表。

new colorListener(parent, Color.LIGHT_GRAY);吓到我了,因为它设置了一堆强引用的对象,这些对象不容易被取消引用,我也不清楚这样做的意图。

parent.revalidate();并没有按照您认为的方式进行。

revalidate生成新的布局遍历,您似乎想要的是repaint

这些...

container.setPreferredSize(containerSize);
container.setBounds(height / 4, height / 4, containerSize.width, containerSize.height);

只是个坏主意。让容器的内容与布局管理器一起处理。

因此,简短的答案是,您还有很多研究要做,例如:

  • OO设计模式
  • OO良好实践,包括“职责分离”,“代码分离”以及更一般的意义上的“依赖注入”
  • Model-View-Controller,编码为接口而不是实现

仅举几例