如何在背景图像上绘制图像 [英] How to draw an image over a background image

查看:99
本文介绍了如何在背景图像上绘制图像的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

嘿所以我试图为学校项目制作D& D游戏。当我绘制背景图像时,它可以工作并绘制它。但是当我绘制背景图像和我的播放器图像时,只显示播放器图像。
我的代码

Hey so im trying to make a D&D game for a school project. When I draw my background image it works and draws it. But when I draw my background image and my player image only the player image shows up. My Code

推荐答案

所以,你的基本问题可以在这里总结出来......

So, your basic problem can be summed up here...

public class UIFrame extends JFrame {
     //...
     public UIFrame() {
        setSize(size);
        add(background);
        add(userScreen);

这有两个问题。首先, JFrame 使用 BorderLayout 来布局它的组件,这意味着实际上只会添加最后添加的组件。

Two thins are wrong with this. First, JFrame uses a BorderLayout to layout it's components, meaning that only the last component added will actually be laid out.

请参阅在容器中布置组件以获取更多详细信息

See Laying Out Components Within a Container for more details

其次,组件以LIFO顺序绘制,因此在您的情况下,这将是 userScreen 然后背景 - 但由于背景从未布局(它的大小是 0x0 )你不会看到它。

Second, components are painted in LIFO order, so in your case, that would be userScreen then background - but since background is never laid out (it's size is 0x0) you won't see it.

我提到它的原因是因为,你不应该尝试设计你这样编程。

The reason I mention it is because, you shouldn't be trying to design you program this way.

下一期......

public static Image[] scheme_player = new Image[1];
public static boolean imagesLoaded = false;
public static boolean leftPress = false;
public static boolean rightPress = false;
public static boolean upPress = false;
public static boolean downPress = false;
public static boolean mouseClick = false;

这是一个糟糕设计的好兆头。 static 不是你的朋友,可能导致一些非常有趣(并且难以调试)的行为

This is a pretty good sign of a bad design. static is not your friend and can lead to some very interesting (and difficult to debug) behaviour

相反,我们我想以更加不可知的方式思考你的设计。

Instead, we want to think about your design from in a more agnostic manner.

这是一个例子 ,旨在提供一个思路的基线,您可以从中发展自己的API /概念。示例中有一些内容,如果我设计自己的解决方案,我可能会采取不同的做法,但为了简洁起见并降低总体复杂性

This is an "example", intended to provide a "base line" of ideas from which you can grow your own API/concept from. There are some things in the example which I might do differently if I were designing my own solution, but have been done for brevity and to reduce the overall complexity

你有涂料的东西,你有移动的东西,你可能有碰撞的东西和其他的东西。你需要一些方法来管理和协调所有这些东西

You have stuff that paints, you have stuff that moves, you probably have stuff that collides and other stuff to. You need some way to manage and coordinate all this "stuff"

OOP的基本原理(以及一般的编程)是模型 - 视图 - 控制器的概念。我们的想法是将您的概念分成较小的,分离的工作单元,然后可以按照您需要的方式将它们重新组合在一起,形成一幅大图(或工作体)

A basic principle of OOP (and programming in general) is the concept of "model-view-controller". The idea is to seperate your concepts into smaller, decoupled units of work, which can then be put back together, in what ever way you need, to form a large picture (or body of work)

所以,让我们从基本构建模块实体开始。程序中的实体包含信息,可以执行不同的任务和角色。因为你可能有任意数量的实体类型,我会从一系列描述不同类型实体的基本行为的基本接口开始......

So, let's start with the basic building blocks, the "entity". A "entity" is something in your program which carries information and may perform different tasks and roles. Because you might have any number of types of entities, I would start with a series of basic interfaces which describe basic behaviour of different types of entities...

public interface Entity {
    // Other common properties
}

public interface MovableEntity extends Entity {
    public void move(GameModel model);
    // Other "movable" entities might also have
    // speed adjustments, but I might consider those as
    // seperate entities in of themselves, but that's me
}

public interface PaintableEntity extends Entity {
    public void paint(Graphics2D g2d, GameModel model);
}

注意,我没有将动产或油漆制成一个实体(你这可能会创建第四个接口,这是因为您可能有一个未绘制的可移动实体或一个不移动的绘制实体,如背景!

Notice, I didn't make movable or paintable into a single entity (you could create a fourth interface which does), this is because you might have a movable entity which isn't painted or a painted entity which doesn't move, like the background!

接下来,我们需要一些容器来保存游戏其他部分可能需要的重要信息,以便完成他们的工作,某种模型!

Next, we need some kind container to hold the important information which the rest of the game might need in order to do their jobs, some kind of, model!

public interface GameModel {

    public enum Input {
        UP, DOWN, LEFT, RIGHT;
    }

    public boolean hasInput(Input input);
    public Dimension getViewableArea();

    // Other properties which might be needed by the
    // entities
}

这是非常基本的,但这是共享信息的概念。在这里,我只关注实体真正需要的那些元素(不需要公开他们真正不需要的功能)。

This is pretty basic, but this is the concept around which information is shared. Here I've focused only on those elements which the entities really needed (no need to expose functionality that they don't really need).

我还准备了输入的状态,与输入实际发生的方式无关。 API的其余部分并不关心,他们只想知道输入是否可用。

I've also prepared the state for "input", which is agnostic from how that input actually occurs. The rest of the API doesn't care, they only want to know if the input is available or not.

从此,我可以开始构建一些基本实体.. 。

From this, I can start building some basic entities...

public class BackgroundEntity implements PaintableEntity {

    @Override
    public void paint(Graphics2D g2d, GameModel model) {
        g2d.setColor(Color.BLUE);
        Dimension bounds = model.getViewableArea();
        g2d.fillRect(0, 0, bounds.width, bounds.height);
    }

}

public class PlayerEntity implements PaintableEntity, MovableEntity {

    private int speed = 2;
    private int x, y;

    public PlayerEntity() {
        // load the player image
    }

    public int getSpeed() {
        return speed;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    public void setX(int x) {
        this.x = x;
    }

    public void setY(int y) {
        this.y = y;
    }

    @Override
    public void paint(Graphics2D g2d, GameModel model) {
        g2d.setColor(Color.RED);
        g2d.drawRect(getX(), getY(), 10, 10);
    }

    @Override
    public void move(GameModel model) {
        int speed = getSpeed();
        int x = getX();
        int y = getY();

        if (model.hasInput(GameModel.Input.UP)) {
            y -= speed;
        } else if (model.hasInput(GameModel.Input.DOWN)) {
            y += speed;
        }
        if (model.hasInput(GameModel.Input.LEFT)) {
            x -= speed;
        } else if (model.hasInput(GameModel.Input.RIGHT)) {
            x += speed;
        }

        Dimension bounds = model.getViewableArea();
        if (y < 0) {
            y = 0;
        } else if (y + 10 > bounds.height) {
            y = bounds.height - 10;
        }
        if (x < 0) {
            x = 0;
        } else if (x + 10 > bounds.width) {
            x = bounds.width - 10;
        }

        setX(x);
        setY(y);
    }

}

好的,这一切都很好,很好,但我们需要某种方式来实际更新状态,在这种情况下,我们设计了一个可变模型的概念。意图是隐藏功能,因此我们不会将其暴露给不需要的API部分(播放器不需要能够添加/删除新实体)

Okay, that's all fine and good, but we need some way to actually update the state, in this case, we devise a concept of mutable model. The intent been to "hide" functionality, so we don't expose it to parts of the API which don't need (the player doesn't need to be able to add/remove new entities)

public interface MutableGameModel extends GameModel {

    public void setInput(Input input, boolean enabled);

    public void add(Entity entity);
    public void remove(Entity entity);

    public void update();

    // Decision, who needs access to these lists
    public List<PaintableEntity> paintableEntities();
    public List<MovableEntity> moveableEntities();
}

因为我们确实需要某种方式来工作......

And because we actually need some way to work...

public class DefaultGameModel implements MutableGameModel {

    private Set<Input> inputs = new HashSet<>();
    private List<Entity> entities = new ArrayList<>(25);

    @Override
    public boolean hasInput(Input input) {
        return inputs.contains(input);
    }

    @Override
    public void setInput(Input input, boolean enabled) {
        if (enabled) {
            inputs.add(input);
        } else {
            inputs.remove(input);
        }
    }

    @Override
    public Dimension getViewableArea() {
        return new Dimension(400, 400);
    }

    public void update() {
        for (MovableEntity entity : moveableEntities()) {
            entity.move(this);
        }
    }

    // This is not the most efficent approach. You might consider 
    // caching each entity type into seperate lists when they are added
    // instead
    public List<PaintableEntity> paintableEntities() {
        return entities.stream()
                        .filter(e -> e instanceof PaintableEntity)
                        .map(e -> (PaintableEntity) e)
                        .collect(Collectors.toList());
    }

    public List<MovableEntity> moveableEntities() {
        return entities.stream()
                        .filter(e -> e instanceof MovableEntity)
                        .map(e -> (MovableEntity) e)
                        .collect(Collectors.toList());
    }

    @Override
    public void add(Entity entity) {
        entities.add(entity);
    }

    @Override
    public void remove(Entity entity) {
        entities.remove(entity);
    }

}

这是非常基本的概念,但是它为其余的工作奠定了基础 - 请不要这是一个示例,我为了简洁而削减了一些角落,因此我可以快速启动并运行,但基本概念应该坚持

This is pretty basic concept, but it puts into place the foundations of the rest of work - please not this is a "EXAMPLE", I've cut some corners for brevity and so I could get it up and running quickly, but the basic concept should hold up

最后,我们进入视图(和控制器)。这是我们监视输入状态,相应更新模型,运行主循环以更新模型状态,基于当前输入,计划和执行绘画的部分

And finally, we get to the "view" (and controller). This is the part where we monitor for input states, update the model accordingly, run the main loop to update the model state, based on the current inputs, schedule and performing painting

public class GamePane extends JPanel {

    private MutableGameModel model;

    public GamePane(MutableGameModel model) {
        this.model = model;

        setupBindings();

        Timer timer = new Timer(5, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                model.update();
                repaint();
            }
        });
        timer.start();
    }

    public void setupBindings() {
        InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
        ActionMap actionMap = getActionMap();

        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), "Up.pressed");
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, true), "Up.released");
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), "Down.pressed");
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, true), "Down.released");
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), "Left.pressed");
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, true), "Left.released");
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), "Right.pressed");
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, true), "Right.released");

        actionMap.put("Up.pressed", new InputAction(model, GameModel.Input.UP, true));
        actionMap.put("Up.released", new InputAction(model, GameModel.Input.UP, false));
        actionMap.put("Down.pressed", new InputAction(model, GameModel.Input.DOWN, true));
        actionMap.put("Down.released", new InputAction(model, GameModel.Input.DOWN, false));
        actionMap.put("Left.pressed", new InputAction(model, GameModel.Input.LEFT, true));
        actionMap.put("Left.released", new InputAction(model, GameModel.Input.LEFT, false));
        actionMap.put("Right.pressed", new InputAction(model, GameModel.Input.RIGHT, true));
        actionMap.put("Right.released", new InputAction(model, GameModel.Input.RIGHT, false));
    }

    @Override
    public Dimension getPreferredSize() {
        return model.getViewableArea().getSize();
    }

    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        for (PaintableEntity entity : model.paintableEntities()) {
            Graphics2D g2d = (Graphics2D) g.create();
            entity.paint(g2d, model);
            g2d.dispose();
        }
    }

}

public class InputAction extends AbstractAction {

    private MutableGameModel model;
    private GameModel.Input input;
    private boolean pressed;

    public InputAction(MutableGameModel model, GameModel.Input input, boolean pressed) {
        this.model = model;
        this.input = input;
        this.pressed = pressed;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        model.setInput(input, pressed);
    }

}

非常简单:P

该解决方案使用键绑定通常不那么麻烦 KeyListener 并且应该在99%的情况下用于监视输入子集。

The solution makes use of the Key Bindings which is generally less troublesome them KeyListener and should be used in 99% of cases you want to monitor a subset of input.

我还使用了 Swing 计时器 作为我的主循环。这是一个更安全的选项(在Swing中),因为它不违反API的单线程性质

I've also used a Swing Timer as my "main-loop". This is a safer option (in Swing) as it does not violate the single threaded nature of the API

因为我知道尝试将代码片段组合在一起有多难...

Because I know how hard it can be to try and put together "snippets" of code...

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                DefaultGameModel model = new DefaultGameModel();
                model.add(new BackgroundEntity());
                model.add(new PlayerEntity());

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new GamePane(model));
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class GamePane extends JPanel {

        private MutableGameModel model;

        public GamePane(MutableGameModel model) {
            this.model = model;

            setupBindings();

            Timer timer = new Timer(5, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    model.update();
                    repaint();
                }
            });
            timer.start();
        }

        public void setupBindings() {
            InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
            ActionMap actionMap = getActionMap();

            inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), "Up.pressed");
            inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, true), "Up.released");
            inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), "Down.pressed");
            inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, true), "Down.released");
            inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), "Left.pressed");
            inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, true), "Left.released");
            inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), "Right.pressed");
            inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, true), "Right.released");

            actionMap.put("Up.pressed", new InputAction(model, GameModel.Input.UP, true));
            actionMap.put("Up.released", new InputAction(model, GameModel.Input.UP, false));
            actionMap.put("Down.pressed", new InputAction(model, GameModel.Input.DOWN, true));
            actionMap.put("Down.released", new InputAction(model, GameModel.Input.DOWN, false));
            actionMap.put("Left.pressed", new InputAction(model, GameModel.Input.LEFT, true));
            actionMap.put("Left.released", new InputAction(model, GameModel.Input.LEFT, false));
            actionMap.put("Right.pressed", new InputAction(model, GameModel.Input.RIGHT, true));
            actionMap.put("Right.released", new InputAction(model, GameModel.Input.RIGHT, false));
        }

        @Override
        public Dimension getPreferredSize() {
            return model.getViewableArea().getSize();
        }

        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            for (PaintableEntity entity : model.paintableEntities()) {
                Graphics2D g2d = (Graphics2D) g.create();
                entity.paint(g2d, model);
                g2d.dispose();
            }
        }

    }

    public class InputAction extends AbstractAction {

        private MutableGameModel model;
        private GameModel.Input input;
        private boolean pressed;

        public InputAction(MutableGameModel model, GameModel.Input input, boolean pressed) {
            this.model = model;
            this.input = input;
            this.pressed = pressed;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            model.setInput(input, pressed);
        }

    }

    public class DefaultGameModel implements MutableGameModel {

        private Set<Input> inputs = new HashSet<>();
        private List<Entity> entities = new ArrayList<>(25);

        @Override
        public boolean hasInput(Input input) {
            return inputs.contains(input);
        }

        @Override
        public void setInput(Input input, boolean enabled) {
            if (enabled) {
                inputs.add(input);
            } else {
                inputs.remove(input);
            }
        }

        @Override
        public Dimension getViewableArea() {
            return new Dimension(400, 400);
        }

        public void update() {
            for (MovableEntity entity : moveableEntities()) {
                entity.move(this);
            }
        }

        // This is not the most efficent approach. You might consider 
        // caching each entity type into seperate lists when they are added
        // instead
        public List<PaintableEntity> paintableEntities() {
            return entities.stream()
                            .filter(e -> e instanceof PaintableEntity)
                            .map(e -> (PaintableEntity) e)
                            .collect(Collectors.toList());
        }

        public List<MovableEntity> moveableEntities() {
            return entities.stream()
                            .filter(e -> e instanceof MovableEntity)
                            .map(e -> (MovableEntity) e)
                            .collect(Collectors.toList());
        }

        @Override
        public void add(Entity entity) {
            entities.add(entity);
        }

        @Override
        public void remove(Entity entity) {
            entities.remove(entity);
        }

    }

    public interface GameModel {

        public enum Input {
            UP, DOWN, LEFT, RIGHT;
        }

        public boolean hasInput(Input input);

        public Dimension getViewableArea();

        // Other properties which might be needed by the
        // entities
    }

    public interface MutableGameModel extends GameModel {

        public void setInput(Input input, boolean enabled);

        public void add(Entity entity);
        public void remove(Entity entity);

        public void update();

        // Decision, who needs access to these lists
        public List<PaintableEntity> paintableEntities();
        public List<MovableEntity> moveableEntities();
    }

    public interface Entity {
        // Other common properties
    }

    public interface MovableEntity extends Entity {

        public void move(GameModel model);
        // Other "movable" entities might also have
        // speed adjustments, but I might consider those as
        // seperate entities in of themselves, but that's me
    }

    public interface PaintableEntity extends Entity {

        public void paint(Graphics2D g2d, GameModel model);
    }

    public class BackgroundEntity implements PaintableEntity {

        @Override
        public void paint(Graphics2D g2d, GameModel model) {
            g2d.setColor(Color.BLUE);
            Dimension bounds = model.getViewableArea();
            g2d.fillRect(0, 0, bounds.width, bounds.height);
        }

    }

    public class PlayerEntity implements PaintableEntity, MovableEntity {

        private int speed = 2;
        private int x, y;

        public PlayerEntity() {
            // load the player image
        }

        public int getSpeed() {
            return speed;
        }

        public int getX() {
            return x;
        }

        public int getY() {
            return y;
        }

        public void setX(int x) {
            this.x = x;
        }

        public void setY(int y) {
            this.y = y;
        }

        @Override
        public void paint(Graphics2D g2d, GameModel model) {
            g2d.setColor(Color.RED);
            g2d.drawRect(getX(), getY(), 10, 10);
        }

        @Override
        public void move(GameModel model) {
            int speed = getSpeed();
            int x = getX();
            int y = getY();

            if (model.hasInput(GameModel.Input.UP)) {
                y -= speed;
            } else if (model.hasInput(GameModel.Input.DOWN)) {
                y += speed;
            }
            if (model.hasInput(GameModel.Input.LEFT)) {
                x -= speed;
            } else if (model.hasInput(GameModel.Input.RIGHT)) {
                x += speed;
            }

            Dimension bounds = model.getViewableArea();
            if (y < 0) {
                y = 0;
            } else if (y + 10 > bounds.height) {
                y = bounds.height - 10;
            }
            if (x < 0) {
                x = 0;
            } else if (x + 10 > bounds.width) {
                x = bounds.width - 10;
            }

            setX(x);
            setY(y);
        }

    }

}

nb - 对于那些想要告诉我们使用 List#stream 这种方式是多么低效的人,是的,你是对的,我已多次提到 - 使用的重点是简洁。

nb - For every one whose going to tell more how horribly inefficient it is to use List#stream this way, yes, you are correct, I've mentioned that several times - the point for using is brevity.

如果我这样做,我要约束 MutableModelLevel 的要求(使用 add(PaintableEntity)或让add方法确定需要添加实体的 List 系列,或者制作一个一系列限制功能的通用模型,所以当我设计我的实现时,我可以选择我想要使用的功能 - 但那只是我

If I was doing this, I'd either constraint the requirements at the MutableModelLevel (using things like add(PaintableEntity) or have the add method determine which series of Lists the entity needs to be added to) or make a series of generic "models" which constraint the functionality, so that when I devise my implementation, I can pick and choose which functionality I want to use - but that's just me

这篇关于如何在背景图像上绘制图像的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆