Как создать интерактивные панели с изменением текстур и цветов в Java? - PullRequest
0 голосов
/ 14 декабря 2018

Я новичок здесь, но перед публикацией я провел небольшое исследование.Моя цель - создать простую игру Tower Defense, используя пару интересных идей и, кроме того, обучить моим навыкам разработки с использованием javax.swing и java.awt.Насколько я знаю, разработчики в основном ленивые парни, и они делают все, чтобы сделать их жизнь проще.

Существует карта с сеткой, и для загрузки карты моя игра использует логическую матрицу и метод загрузки для определения местоположения местности на панелях.Я думал, что это будет довольно простое решение.Поскольку матрица имеет размер 12 x 12, я хотел бы создать ее с другим приложением, а не вводить строку из 144 чисел.

Здесь возникает идея сначала создать приложение-редактор карт, а затем создавать карты для уровней.в этом.Когда у меня есть такой инструмент, я могу сделать эту карту визуально, а затем сохранить ее логическую матрицу в файл, который позже можно будет прочитать с помощью метода загрузки и создать заново в игре.Следующим шагом является создание графики, а также панелей, которые будут правильно реагировать на действия пользователя.Слева находится панель с кнопками - после того, как пользователь нажмет одну из них, поле currentColor изменится.

Это поле используется методом, который реализует actionListener и производит изменение цвета панели, объявленной в ее конструкторе.,Я хотел изменить цвет определенной панели, когда она нажата.Я использую цвета, потому что сейчас его легче заставить работать, позже я хочу заменить цвет на текстуру - очевидно, я знаю, что должен использовать метод paintComponent, но я предполагаю, что он тоже будет работать, верно?Также было бы неплохо, если бы граница панели меняла цвет, когда я перемещал курсор над ней, и возвращал ее в нормальное состояние, когда мышь находится где-то еще.

Дело в том, что у меня возникли некоторые проблемы, чтобы сделать панели интерактивными,Первая проблема заключается в том, что панели создаются в цикле for, и это затрудняет обращение к определенной панели, когда мышь находится над ней.Еще одна проблема заключается в том, что я хотел бы изменить внешний вид этой панели после нажатия на нее.

Насколько я знаю, MouseListeners должны выполнять свою работу, но как на самом деле написать ее, чтобы иметь эффект на экране?Я нашел какой-то пост об этом, но для меня это не работает.Вот ссылка: Подсветка панелей в 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 голосов
/ 15 декабря 2018

Вопрос широкий, а ответ сложный.

По сути, вы хотите провести некоторое исследование таких понятий, как «разделение обязанностей» и «разделение кода».

Идея состоит в том,что вы нарушаете свои функциональные требования, чтобы ваши объекты выполняли одну специализированную работу.Вы также «разъединяете» код, чтобы изменение реализации одной части не оказывало негативного влияния на другие части программы.Обычно это достигается за счет использования интерфейсов.

Вы также захотите исследовать концепцию «модель-представление-контроллер», где под «данными» или «состоянием» моделируется один или несколько классов., но полностью не зависит от пользовательского интерфейса.Пользовательский интерфейс затем может «визуализировать» модель так, как он считает нужным.

Таким образом, «представление» (взаимодействующее с контроллером) может изменить состояние (или отреагировать на изменение).в состоянии) модели, что облегчает управление (не серьезно, это так)

Code Review ...

This ...

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

isопасная и плохая идея.Это аннулирует концепцию инкапсуляции и позволяет любому создавать новый экземпляр container в любое время без уведомления, что отсоединит его от того, что ранее использовала программа.На самом деле, вы действительно делаете это.

static не ваш друг.При правильном использовании это полезно, но при таком способе это просто плохая идея, и ее следует избегать.

Вместо этого следует отдавать предпочтение «внедрению зависимостей», когда передаются «элементы», на которые опирается любой объектк нему.

Я бы избегал таких вещей, как ...

this.setSize(800, 600);

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

Окна - это сложные компоненты, которые также содержат декорации окон, которые переносятся по содержимому.Это означает, что доступное пространство для содержимого составляет window size - window decorations.Вместо.Вы должны полагаться на API менеджера компоновки, чтобы обеспечить соответствующие подсказки по размерам и pack фрейм.

В большинстве современных ОС у вас есть «другие» системные элементы, что, опять же, уменьшает объем доступного пространства наэкран (доки, панели задач, другие интересные вещи).Вместо этого вы можете использовать setLocationRelativeTo(null) для более надежного центрирования окна на экране.

Вместо setIconImage следует использовать Window#setIconImages(List), что позволяет вам передаватьколичество изображений, которые могут использоваться API для представления приложения в разных местах, для которых требуются изображения с различным разрешением.

Не уверен, что ...

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

, но это не очень помогает.

Если вы обнаружите, что просто создаете экземпляр класса без фактической поддержки ссылки на него, то это, вероятно, хороший признак плохого дизайна.

Ваш класс Map вызывает кучувопросы, на которые нелегко ответить.Меня это беспокоит, что класс Map изменяет состояние родительского класса и вместо этого выкрикивает «внедрение зависимостей».

Этого ...

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

лучше избегать,Вы должны предпочесть переопределение getPreferredSize, и оно должно просто возвращать «желаемый» размер, который затем может использоваться такими вещами, как GridLayout, для более эффективного размещения компонента.

Это затем приводит к «разделениюобязанность".В этом разделе предполагается, что у вас должен быть класс «плитки», который будет самостоятельно управляться и отвечать за один элемент из модели.

В обработке событий мыши существует ряд ошибок, которые могут возникнуть ...

@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 s, так что запутайтесь прямо здесь.

Вы можете более легко использовать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);

- это просто плохие идеи.Позвольте содержимому контейнера вместе с менеджером макета разобраться.

Итак, краткий ответ: у вас осталось много исследований, таких как:

  • Шаблоны проектирования ОО
  • Передовой опыт ОО, включая «разделение обязанностей», «разделение кода» и, в более общем смысле, «внедрение зависимостей»
  • Model-View-Controller, кодирование в интерфейсвместо реализации

просто назвать несколько

...