如何在背景图像上绘制图像

时间:2018-05-30 19:50:22

标签: java image swing

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

2 个答案:

答案 0 :(得分:0)

你的球员有透明度吗?如果玩家没有alpha,那么它将覆盖bg。

另外,我建议使用此渲染方法,并结合正确的游戏循环:

private void render() {
    BufferStrategy bs = getBufferStrategy();

    if (bs == null) {
        createBufferStrategy(3);
        return;
    }

    Graphics2D g2d = (Graphics2D) bs.getDrawGraphics();

    g2d.setColor(Color.black);
    g2d.fillRect(0, 0, WIDTH, HEIGHT); // background (can also be an image)

    player.render(g2d); // player

    g2d.dispose();
    bs.show();
}

答案 1 :(得分:0)

所以,你的基本问题可以总结一下......

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

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

有关详细信息,请参阅Laying Out Components Within a Container

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

我提到它的原因是,你不应该试图用这种方式设计你的程序。

下一期......

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不是你的朋友,可能导致一些非常有趣(并且难以调试)的行为

相反,我们希望以更加不可知的方式思考您的设计。

声明

这是一个"示例",旨在提供"基线"从中可以发展自己的API /概念的想法。在示例中有一些内容,如果我设计自己的解决方案,我可能会采取不同的做法,但为了简洁和降低整体复杂性已经完成了

基本概念

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

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

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

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);
}

注意,我没有将可移动或可绘制成单个实体(你可以创建第四个interface),这是因为你可能有一个没有被绘制的可移动实体或者像背景一样无法移动的彩绘实体!

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

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
}

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

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

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

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);
    }

}

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

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 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);
    }

}

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

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

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

该解决方案使用Key BindingsKeyListener通常不那么麻烦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); } } } ,并且应该在99%的情况下用于监控输入子集。

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

实施例...

因为我知道尝试和整理这些内容有多么困难&#34;片段&#34;代码......

List#stream

nb - 对于每一个要告诉我们以这种方式使用MutableModelLevel多么低效率的人来说,是的,你是对的,我已多次提到过 - 使用的重点是简洁。

如果我这样做,我要约束add(PaintableEntity)上的要求(使用List之类的内容,或者让add方法确定哪个系列的{{1}}是实体需要添加到)或制作一系列通用&#34;模型&#34;这限制了功能,所以当我设计我的实现时,我可以选择我想要使用的功能 - 但那只是我