JLabel 和 JLayeredPane - 如何在另一个图像上显示一个图像?

JLabel and JLayeredPane - How to display an image over another image?

我尝试在 java 中创建一个小游戏,但我遇到了麻烦。

当我绘制地图时,如果不覆盖正方形的标题集,我将无法显示字符。

我的目标是能够在同一个方块上显示很多图片(比如草地、角色和树的标题集),所以我必须处理图片的透明度(这不是问题)和层(这是问题所在)。

那么如何在另一张图片上显示一张图片?

我如何向 java 解释我需要在另一张图片之上或之下显示这张图片?

这是我的源代码。不知道能不能帮到你。如果你给我一个线索或一个能够管理这些层的功能,你的帮助对我来说真的很有帮助。为我重写所有代码是没用的 x) 这个程序不完整,我现在只用它来测试我的程序。我知道他刷新了两次地图,所以他覆盖了角色的正方形(而且他还有许多其他小故障),但这不是我问题的目的。我试着一步步完成我的游戏!

import javax.swing.JFrame;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;


public class Window extends Thread
{
    private static JFrame window = new JFrame("game");

    public void run()
    {
        Map map = new Map();
        Characters characters = new Characters();

        window.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        window.setSize(Settings.sizeX, Settings.sizeY);
        window.setLocationRelativeTo(null);
        window.setResizable(false);
        window.setVisible(true);

        map.start();
        characters.start();
    }

    private static void reload() throws Exception
    {
        SwingUtilities.updateComponentTreeUI(window);
    }

    private static class Map extends Thread
    {
        private int numberSquareX = Settings.sizeX / 20 + 1;
        private int numberSquareY = Settings.sizeY / 20 + 1;
        private JLabel square[][] = new JLabel[numberSquareX][numberSquareY];

        public void run()
        {
            for (int x = 0, y = 0; y < numberSquareY; x++)
            {
                square[x][y] = new JLabel(new ImageIcon("grass_1.png"));
                square[x][y].setBounds(x * 20, y * 20, 20, 20);
                window.add(square[x][y]);
                if (x == numberSquareX - 1)
                {
                    y++;
                    x = -1;
                }
            }
            square[numberSquareX - 1][numberSquareY - 1] = new JLabel(new ImageIcon("grass_1.png"));
            square[numberSquareX - 1][numberSquareY - 1].setBounds(numberSquareX * 20, numberSquareY * 20, 20, 20);
            window.add(square[numberSquareX - 1][numberSquareY - 1]);
            try
            {
                reload();
            }
            catch (Exception e)
            {

            }
            return;
        }
    }

    private class Characters extends Thread
    {
        private JLabel square[][] = new JLabel[1][1];

        public void run()
        {
            square[0][0] = new JLabel(new ImageIcon("character_1.png"));
            square[0][0].setBounds(Test.posX, Test.posX, 20, 20);
            window.add(square[0][0]);
            try
            {
                reload();
            }
            catch (Exception e)
            {

            }
            return;
        }
    }
}

我已经找到了这个主题:How to use JLayered Pane to display an image on top of another image? and this one Best practice for creating a composite image output for Java Swing 但它们并没有真正帮助我...

我继续寻找答案。如果我找到它,我会回来 post 这里。

已解决。

感谢 MadProgrammer 的评论。

Render the title map to a BufferedImage, either in it's entirety or based on the available viewable area, which ever is more efficient. Paint this to the screen, then paint your character on top it – MadProgrammer

In 15+ years of professional Java/Swing development, I've never found a need to use SwingUtilities.updateComponentTreeUI(window);, instead, simply call repaint on the component which is responsible for renderer the output, I'm pretty sure, you'll find this more efficient. – MadProgrammer

Swing is also a single threaded environment AND is not thread safe, you should NOT be update the UI from outside of the context of the Event Dispatching Thread, as this will setup a race condition and could result in unwanted and difficult to resolve graphical issues. – MadProgrammer

Hint. JLabel is a descendent of Container, which means it can contain other components ;) – MadProgrammer

Thanks a lot MadProgrammer! So I have replace SwingUtilities.updateComponentTreeUI(window) by window.repaint(). You had right for the Thread safe, my map had some bugs but I wasn't able to find where they was from. And what about the BufferedImage? If I create two BufferedImage, the last one can be automatically on the top of the first one? Or I just want to render the title map to a BufferedImage (so I am limited by 2 layers)? – Celine

It will depend on what it is you want to achieve. Using BufferedImages gives you complete control over the placement of the images and yes, one can be rendered over the other, painting is like a artists canvas, as you add things to it, they are added on top of what is already there, BUT, you might find it easier to add a JLabel to another JLabel - just remember, JLabel doesn't have a layout manager by default – MadProgrammer

代码示例:

import javax.swing.JFrame;
import javax.swing.ImageIcon;
import javax.swing.JLabel;


public class Window extends Thread
{
    private static JFrame window = new JFrame("game");
    private int numberSquareX = Settings.sizeX / 20 + 1;
    private int numberSquareY = Settings.sizeY / 20 + 1;
    private JLabel titlesetLayer1[][] = new JLabel[numberSquareX][numberSquareY];
    private JLabel titlesetLayer2[] = new JLabel[1];
    private JLabel titlesetLayer3[] = new JLabel[1];
    private JLabel titlesetLayer4[] = new JLabel[0];
    private JLabel titlesetLayer5[] = new JLabel[0];
    private JLabel characters[] = new JLabel[2];

    public void run()
    {
        window.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        window.setSize(Settings.sizeX, Settings.sizeY);
        window.setLocationRelativeTo(null);
        window.setResizable(false);

        // draw layer5 (on the layer4)

        // draw layer4 (on the layer3)

        // draw layer3 (on the characters)
        titlesetLayer3[0] = new JLabel(new ImageIcon("tree_1.png"));
        titlesetLayer3[0].setBounds(130, 120, 126, 160);
        window.add(titlesetLayer3[0]);

        // draw the charaters
        characters[1] = new JLabel(new ImageIcon("character_1.png"));
        characters[1].setBounds(600, 500, 100, 100);
        window.add(characters[1]);

        characters[0] = new JLabel(new ImageIcon("character_1.png"));
        characters[0].setBounds(100, 100, 100, 100);
        window.add(characters[0]);

        // draw layer2 (under the characters)
        titlesetLayer2[0] = new JLabel(new ImageIcon("tree_1.png"));
        titlesetLayer2[0].setBounds(570, 400, 126, 160);
        window.add(titlesetLayer2[0]);

        // draw layer1 (under the layer2)
        for (int x = 0, y = 0; y < numberSquareY; x++)
        {
            titlesetLayer1[x][y] = new JLabel(new ImageIcon("grass_1.png"));
            titlesetLayer1[x][y].setBounds(x * 20, y * 20, 20, 20);
            window.add(titlesetLayer1[x][y]);
            if (x == numberSquareX - 1)
            {
                y++;
                x = -1;
            }
        }
        titlesetLayer1[numberSquareX - 1][numberSquareY - 1] = new JLabel(new ImageIcon("grass_1.png"));
        titlesetLayer1[numberSquareX - 1][numberSquareY - 1].setBounds(numberSquareX * 20, numberSquareY * 20, 20, 20);
        window.add(titlesetLayer1[numberSquareX - 1][numberSquareY - 1]);

        window.setVisible(true);
        // window.repaint();
    }
}

屏幕截图:

1

另一种解决方案是使用 JLayeredPane!

JLayeredPane layers = new JLayeredPane();
layers.add(tilesetsUnderCharacter, 0); // Layer 0
layers.add(character, 1); // Layer 1
layers.add(tilesetsOnCharacter, 2); // Layer 2
frame.setContentPane(layers);

代码示例:

private void init()
{
    frame.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);
    frame.setSize(Settings.getX(), Settings.getY());
    frame.setResizable(false);
    frame.setLocationRelativeTo(null);

    for (int i = 0; i < y ; i++)
    {
        for (int j = 0; j < x; j++)
        {
            for (int k = 0; k < tilesetsOnCharactersSize; k++)
            {
                tilesetsOnCharacters[i][j][k] = new javax.swing.JLabel(new javax.swing.ImageIcon(Resources.getTileset(Maps.getMapTileset(mapNumber, 1, k, i, j))));
                tilesetsOnCharacters[i][j][k].setBounds(j * tilesetX, i * tilesetY, tilesetX, tilesetY);
                map.add(tilesetsOnCharacters[i][j][k], 4);
            }
            for (int k = 0; k < tilesetsUnderCharactersSize; k++)
            {
                tilesetsUnderCharacters[i][j][k] = new javax.swing.JLabel(new javax.swing.ImageIcon(Resources.getTileset(Maps.getMapTileset(mapNumber, 0, k, i, j))));
                tilesetsUnderCharacters[i][j][k].setBounds(j * tilesetX, i * tilesetY, tilesetX, tilesetY);
                map.add(tilesetsUnderCharacters[i][j][k], 0);
            }
            for (int k = 0; k < mapAttributeSize; k++)
            {
                if (Maps.getMapTileset(mapNumber, 2, k, i, j) == 1)
                {
                    blocked[i][j] = true;
                }
            }
        }
    }
    for (int i = 0; i < charactersNumber; i++)
    {
        characters[i] = new Character(0, 0, 64, 64, 0, 0, 0, 0, 5);
        tilesetsCharacters[i] = new javax.swing.JLabel(new javax.swing.ImageIcon(Characters.getCharacter(characters[i].getCharacterSkin(), characters[i].getDirection())));
        tilesetsCharacters[i].setBounds(characters[i].getX(), characters[i].getY(), characters[i].getSizeX(), characters[i].getSizeY());
        map.add(tilesetsCharacters[i], 1);
        charactersRender[i] = false;
    }

    frame.addKeyListener(new java.awt.event.KeyAdapter()
    {
        @Override
        public void keyTyped(java.awt.event.KeyEvent keyEvent)
        {

        }

        @Override
        public void keyPressed(java.awt.event.KeyEvent keyEvent)
        {
            if((keyEventInt = keyEvent.getKeyCode()) == java.awt.event.KeyEvent.VK_F)
            {
                right = true;
            }
            else if(keyEventInt == java.awt.event.KeyEvent.VK_S)
            {
                left = true;
            }
            else if(keyEventInt == java.awt.event.KeyEvent.VK_E)
            {
                up = true;
            }
            else if(keyEventInt == java.awt.event.KeyEvent.VK_D)
            {
                down = true;
            }
        }

        @Override
        public void keyReleased(java.awt.event.KeyEvent keyEvent)
        {
            if((keyEventInt = keyEvent.getKeyCode()) == java.awt.event.KeyEvent.VK_F)
            {
                right = false;
            }
            else if(keyEventInt == java.awt.event.KeyEvent.VK_S)
            {
                left = false;
            }
            else if(keyEventInt == java.awt.event.KeyEvent.VK_E)
            {
                up = false;
            }
            else if(keyEventInt == java.awt.event.KeyEvent.VK_D)
            {
                down = false;
            }
        }
    });

    frame.setContentPane(map);
    frame.setVisible(true);
}

private void update()
{
    if (exit && characters[0].getX() < x * tilesetX - characters[0].getSizeX() - characters[0].getMovementSpeed() && characters[0].getX() > 0 && characters[0].getY() > 0 && characters[0].getY() < y * tilesetY - characters[0].getSizeY() - characters[0].getMovementSpeed())
    {
        exit = false;
    }
    if (right && (exit || (characters[0].getX() < x * tilesetX - characters[0].getSizeX() - characters[0].getMovementSpeed() && !blocked[characters[0].getY() / tilesetY][(characters[0].getX() + characters[0].getSizeX() + characters[0].getMovementSpeed()) / tilesetX] && !blocked[(characters[0].getY() + characters[0].getSizeY()) / tilesetY][(characters[0].getX() + characters[0].getSizeX() + characters[0].getMovementSpeed()) / tilesetX])))
    {
        characters[0].right();
        characters[0].setScaleX(5);
        if (allowExitRight && characters[0].getX() > x * tilesetX - characters[0].getSizeX() - characters[0].getMovementSpeed() - 1)
            exit = true;
        charactersRender[0] = true;
    }
    if (left && (exit || (characters[0].getX() > 0 && !blocked[characters[0].getY() / tilesetY][(characters[0].getX() - characters[0].getMovementSpeed()) / tilesetX] && !blocked[(characters[0].getY() + characters[0].getSizeY()) / tilesetY][(characters[0].getX() - characters[0].getMovementSpeed()) / tilesetX])))
    {
        characters[0].left();
        characters[0].setScaleX(-3);
        if (allowExitLeft && characters[0].getX() <= 0)
            exit = true;
        charactersRender[0] = true;
    }
    if (jumped || up && (exit || (characters[0].getY() > 0 && !blocked[(characters[0].getY() - characters[0].getMovementSpeed()) / tilesetY][characters[0].getX() / tilesetX] && !blocked[(characters[0].getY() - characters[0].getMovementSpeed()) / tilesetY][(characters[0].getX() + characters[0].getSizeX()) / tilesetX])))
    {
        if (!jump)
        {
            characters[0].up();
            characters[0].setScaleY(-3);
            if (allowExitUp && characters[0].getY() <= 0)
                exit = true;
            charactersRender[0] = true;
        }
        else if (!jumped && !falling)
        {
            jumpCurrentDuration = jumpDuration;
            jumped = true;
        }
        else if (--jumpCurrentDuration > 0)
        {
            if (exit || (characters[0].getY() > 0 && !blocked[(characters[0].getY() - characters[0].getMovementSpeed()) / tilesetY][characters[0].getX() / tilesetX] && !blocked[(characters[0].getY() - characters[0].getMovementSpeed()) / tilesetY][(characters[0].getX() + characters[0].getSizeX()) / tilesetX]))
            {
                characters[0].up();
                characters[0].setScaleY(-3);
                if (allowExitUp && characters[0].getY() <= 0)
                    exit = true;
                charactersRender[0] = true;
            }
        }
        else
        {
            jumped = false;
        }
    }
    if (((down && !jumped) || (gravity && !jumped)) && (exit || (characters[0].getY() < y * tilesetY - characters[0].getSizeY() - characters[0].getMovementSpeed() && !blocked[(characters[0].getY() + characters[0].getSizeY() + characters[0].getMovementSpeed()) / tilesetX][characters[0].getX() / tilesetX] && !blocked[(characters[0].getY() + characters[0].getSizeY() + characters[0].getMovementSpeed()) / tilesetY][(characters[0].getX() + characters[0].getSizeX()) / tilesetX])))
    {
        characters[0].down();
        characters[0].setScaleY(5);
        if (allowExitDown && characters[0].getY() > y * tilesetY - characters[0].getSizeY() - characters[0].getMovementSpeed())
            exit = true;
        if (jump)
            falling = true;
        charactersRender[0] = true;
    }
    else if (jump)
        falling = false;
}

private void render()
{
    for (int i = 0; i < charactersNumber; i++)
    {
        if (charactersRender[i])
        {
            tilesetsCharacters[i].setIcon(new javax.swing.ImageIcon(Characters.getCharacter(characters[i].getCharacterSkin(), characters[i].getDirection())));
            tilesetsCharacters[i].setBounds(characters[i].getX() + characters[i].getScaleX(), characters[i].getY() + characters[i].getScaleY(), characters[i].getSizeX(), characters[i].getSizeY());
            charactersRender[i] = false;
        }
    }
}

屏幕截图:

编辑:我还找到了一个名为 Slick2D 的库,它与 TiledMapEditor 一起工作:

如何设置 Slick2D:How to install Slick2d?

如何使用 Slick2D 和 TiledMapEditor:Slick2D + Tiled cant load map

从哪里开始:https://thejavablog.wordpress.com/2008/06/08/using-slick-2d-to-write-a-game/