程序无法正确绘制屏幕

Program not painting screen properly

我一直在构建一个简短的程序,该程序主要是在 JPanel 上绘制宇宙飞船并侦听告诉程序发射子弹的键。问题是它甚至没有在屏幕上绘制宇宙飞船或子弹。我还怀疑 KeyBindings 可能无法正常工作,因为这是以前的问题(我可能已经修复也可能没有修复),但手头的主要问题仍然是我的屏幕没有被绘制。这是我的代码:

public enum Direction {
    LEFT, RIGHT, SPACE
}

import javax.swing.JFrame;

public class Main {
    public static void main(String[] args) {
        JFrame frame;

        Ship s1;
        Shoot shoot;

        // Set the frame up
        frame = new JFrame();
        frame.setSize(400, 300);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setResizable(false);
        frame.setVisible(true);

        // Get some more necessary objects
        s1 = new Ship();
        shoot = new Shoot(s1);
        frame.getContentPane().add(shoot);
        s1.setShoot(shoot);

        // Threads
        Thread ship = new Thread(s1);
        ship.start();
    }
}

import java.awt.Graphics;
import java.awt.event.KeyEvent;

import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JPanel;
import javax.swing.KeyStroke;

public class Shoot extends JPanel {

    Ship s1;

    public Shoot(Ship s1) {
        this.s1 = s1;

        addKeyBinding(KeyEvent.VK_LEFT, "left.pressed", new MoveAction(true, s1, Direction.LEFT), true);
        addKeyBinding(KeyEvent.VK_LEFT, "left.released", new MoveAction(false, s1, Direction.LEFT), false);

        addKeyBinding(KeyEvent.VK_RIGHT, "right.pressed", new MoveAction(true, s1, Direction.RIGHT), true);
        addKeyBinding(KeyEvent.VK_RIGHT, "right.released", new MoveAction(false, s1, Direction.RIGHT), false);

        addKeyBinding(KeyEvent.VK_SPACE, "space.pressed", new MoveAction(true, s1, Direction.SPACE), true);
        addKeyBinding(KeyEvent.VK_SPACE, "space.released", new MoveAction(false, s1, Direction.SPACE), false);

        setDoubleBuffered(true);
    }

    @Override
    public void paintComponent(Graphics g) {
        // Draw the ship
        super.paintComponent(g);
        s1.draw(g);
        g.fill3DRect(40, 50, 10, 10, false);
    }

    protected void addKeyBinding(int keyCode, String name, Action action, boolean keyPressed) {
        if (keyPressed) {
            addKeyBinding(KeyStroke.getKeyStroke(keyCode, 0, false), name, action);
        } else {
            addKeyBinding(KeyStroke.getKeyStroke(keyCode, 0, true), name, action);
        }
    }

    protected void addKeyBinding(KeyStroke keyStroke, String name, Action action) {
        InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
        ActionMap actionMap = getActionMap();
        inputMap.put(keyStroke, name);
        actionMap.put(name, action);
    }

}

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;

public class Ship implements Runnable {
    int x, y, xDirection, bx, by;
    boolean readyToFire, shooting = false;
    Rectangle bullet;
    Shoot shoot;

    public Ship() {
        x = 175;
        y = 275;
        bullet = new Rectangle(0, 0, 3, 5);
    }

    public void draw(Graphics g) {
        // System.out.println("draw() called");
        g.setColor(Color.BLUE);
        g.fillRect(x, y, 40, 10);
        g.fillRect(x + 18, y - 7, 4, 7);
        if (shooting) {
            g.setColor(Color.RED);
            g.fillRect(bullet.x, bullet.y, bullet.width, bullet.height);
        }
        shoot.repaint();
    }

    public void move() {
        x += xDirection;
        if (x <= 0)
            x = 0;
        if (x >= 360)
            x = 360;
        shoot.repaint();
    }

    public void shoot() {
        if (shooting) {
            bullet.y--;
            shoot.repaint();
        }
    }

    public void setXDirection(int xdir) {
        xDirection = xdir;
    }

    public void setShoot(Shoot shoot) {
        this.shoot = shoot;
    }

    @Override
    public void run() {
        try {
            while (true) {
                shoot();
                move();
                Thread.sleep(5);
            }
        } catch (Exception e) {
            System.err.println(e.getMessage());
        }

    }
}

import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.util.HashSet;

import javax.swing.AbstractAction;

public class MoveAction extends AbstractAction {

    boolean pressed;
    Ship s1;
    Direction dir;
    private Set<Direction> movement;

    public MoveAction(boolean pressed, Ship s1, Direction dir) {
        System.out.println("moveaction class");
        movement = new HashSet<Direction>();
        this.pressed = pressed;
        this.s1 = s1;
        this.dir = dir;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        try {
            if (movement.contains(Direction.LEFT)) {
                if (pressed) {
                    s1.setXDirection(-1);
                } else {
                    s1.setXDirection(0);
                }
            } else if (movement.contains(Direction.RIGHT)) {
                if (pressed) {
                    s1.setXDirection(1);
                } else {
                    s1.setXDirection(0);
                }
            } else if (movement.contains(Direction.SPACE)) {
                if (pressed) {
                    if (s1.bullet == null)
                        s1.readyToFire = true;
                    if (s1.readyToFire) {
                        s1.bullet.x = s1.x + 18;
                        s1.bullet.y = s1.y - 7;
                        s1.shooting = true;
                    }
                } else {
                    s1.readyToFire = false;
                    if (s1.bullet.y <= -7) {
                        s1.bullet = null;
                        s1.shooting = false;
                        s1.bullet = null;
                        s1.bullet = new Rectangle(0, 0, 0, 0);
                        s1.readyToFire = true;
                    }
                }
            }
        } catch (NullPointerException ex) {
            System.out.println("NullPointerException");
        }
    }

您的绘图取决于布尔变量拍摄;有一个地方将射击设置为真;如果关键操作不起作用,程序的流程可能永远不会到达那里,也可能永远不会发生。

所以我建议您将项目最小化到一个屏幕,该屏幕无需按键即可绘制图形。

如果能看到图形就可以逐步添加按键

所以,有很多问题...

您应该在 JFrame 上最后调用 setVisible,这将确保组件的布局

您的键绑定问题似乎与您使用机芯 Set 这一事实有关,但您实际上从未向其中添加任何内容。相反,您应该检查 dir 值。

可能还有很多其他事情。您的代码是紧密耦合的,并且没有任何集中的状态管理。

我将从更好地理解 Model-View-Controller 范式开始,并将代码分成适当的责任区域。

游戏的 "data" 应该与游戏的 "rendering" 分开,后者应该与游戏更新方式的决定分开。

我要介绍的是旨在激发创意的过度简化,而不是具体的解决方案,因为有多种方法可以实现物理实现...

因此,我们需要的是游戏中某物的概念,又名 "entity",实体有多种形式,但我将重点关注 renderable/displayable 实体。您需要某种模型来负责对游戏的当前状态进行建模并负责实施规则。您需要某种视图,它负责渲染模型和响应用户输入。您需要某种控制器来控制模型和视图的桥接方式。

从一系列 interfaces 开始总是一个好主意,它们定义了合同期望并概述了预期元素相互通信的预期方式。同样,我采用了一种简单、直接的方法,但这绝不是唯一的...

public static enum Direction {
    LEFT,
    RIGHT,
    SPACE
}

public interface Entity {
    public void paint(Graphics2D g2d);
    public Point getLocation();
    public void setLocation(Point p);
    public Dimension getSize();
}

public interface GameModel {
    public Entity[] getEntities();
    public void update(Rectangle bounds, Set<Direction> keys);
}

public interface GameController {
    public Entity[] getEntities();
    public void setDirection(Direction direction, boolean pressed);
    public void start();
}

public interface GameView {
    public void setController(GameController controller);
    public GameController getController();
    public Rectangle getViewBounds();
    public void repaint();
}

让我们看一下实体的实现。此示例有两个实体,一个 Player 和一个 Bullet...

public abstract class AbstractEntity implements Entity {

    private final Point location = new Point(0, 0);

    @Override
    public Point getLocation() {
        return new Point(location);
    }

    @Override
    public void setLocation(Point p) {
        location.setLocation(p);
    }

}

public class Player extends AbstractEntity {

    public Player(Rectangle bounds) {
        int x = bounds.x + ((bounds.width - getSize().width) / 2);
        int y = bounds.y + (bounds.height - getSize().height);
        setLocation(new Point(x, y));
    }

    @Override
    public Dimension getSize() {
        return new Dimension(40, 17);
    }

    @Override
    public void paint(Graphics2D g2d) {
        Point p = getLocation();
        Dimension size = getSize();
        g2d.setColor(Color.BLUE);
        g2d.fillRect(p.x, p.y + 7, size.width, 10);
        g2d.fillRect(p.x + 18, p.y, 4, 7);
    }

}

public class Bullet extends AbstractEntity {

    @Override
    public void paint(Graphics2D g2d) {
        Rectangle bullet = new Rectangle(getLocation(), getSize());
        g2d.setColor(Color.RED);
        g2d.fill(bullet);
    }

    @Override
    public Dimension getSize() {
        return new Dimension(4, 8);
    }

}

没什么了不起的,但他们每个人都定义了自己的参数,并且可以在被询问时呈现自己的状态。

接下来,我们有模型、控制器和视图。此示例使用控制器作为主要游戏循环,负责更新游戏(模型)状态和安排重绘。这是通过使用 Swing Timer 来完成的,因为这可以防止更新循环和绘画循环之间可能出现的竞争条件。更复杂的实现需要通过使用 BufferStrategy and BufferStrategy and BufferCapabilities.

来接管绘画过程

该模型仅获取当前视图边界和键的当前状态并更新实体的状态。

视图监视用户输入,通知控制器,并呈现游戏的当前状态。

您会注意到视图和模型从不直接相互通信,这是控制器的域。

public class DefaultGameModel implements GameModel {

    private final List<Entity> entities;
    private Player player;

    private Long lastShot;

    public DefaultGameModel() {
        entities = new ArrayList<>(25);
    }

    @Override
    public Entity[] getEntities() {
        return entities.toArray(new Entity[0]);
    }

    @Override
    public void update(Rectangle bounds, Set<Direction> keys) {
        if (player == null) {
            player = new Player(bounds);
            entities.add(player);
        }

        Point p = player.getLocation();
        int xDelta = 0;
        if (keys.contains(Direction.LEFT)) {
            xDelta = -4;
        } else if (keys.contains(Direction.RIGHT)) {
            xDelta = 4;
        }
        p.x += xDelta;
        if (p.x <= bounds.x) {
            p.x = bounds.x;
        } else if (p.x + player.getSize().width >= bounds.x + bounds.width) {
            p.x = bounds.width - player.getSize().width;
        }
        player.setLocation(p);

        Iterator<Entity> it = entities.iterator();
        while (it.hasNext()) {
            Entity entity = it.next();
            if (entity instanceof Bullet) {
                Point location = entity.getLocation();
                Dimension size = entity.getSize();
                location.y -= size.height;
                if (location.y + size.height < bounds.y) {
                    it.remove();
                } else {
                    entity.setLocation(location);
                }
            }
        }

        if (keys.contains(Direction.SPACE)) {
            if (lastShot == null || System.currentTimeMillis() - lastShot > 100) {
                lastShot = System.currentTimeMillis();
                Bullet bullet = new Bullet();
                int x = p.x + ((player.getSize().width - bullet.getSize().width) / 2);
                int y = p.y - bullet.getSize().height;
                bullet.setLocation(new Point(x, y));

                entities.add(bullet);
            }
        }
    }

}

public class DefaultGameController implements GameController {

    private GameModel model;
    private GameView view;

    private Timer timer;

    private Set<Direction> keys = new HashSet<>(25);

    public DefaultGameController(GameModel gameModel, GameView gameView) {
        gameView.setController(this);

        view = gameView;
        model = gameModel;
    }

    @Override
    public Entity[] getEntities() {
        return model.getEntities();
    }

    @Override
    public void setDirection(Direction direction, boolean pressed) {
        if (pressed) {
            keys.add(direction);
        } else {
            keys.remove(direction);
        }
    }

    @Override
    public void start() {
        if (timer != null && timer.isRunning()) {
            timer.stop();
        }
        timer = new Timer(40, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                model.update(view.getViewBounds(), Collections.unmodifiableSet(keys));
                view.repaint();
            }
        });
        timer.start();
    }

}

public class DefaultGameView extends JPanel implements GameView {

    private GameController controller;

    public DefaultGameView() {
        addKeyBinding("left.pressed", KeyEvent.VK_LEFT, true, new DirectionAction(Direction.LEFT, true));
        addKeyBinding("left.released", KeyEvent.VK_LEFT, false, new DirectionAction(Direction.LEFT, false));
        addKeyBinding("right.pressed", KeyEvent.VK_RIGHT, true, new DirectionAction(Direction.RIGHT, true));
        addKeyBinding("right.released", KeyEvent.VK_RIGHT, false, new DirectionAction(Direction.RIGHT, false));
        addKeyBinding("space.pressed", KeyEvent.VK_SPACE, true, new DirectionAction(Direction.SPACE, true));
        addKeyBinding("space.released", KeyEvent.VK_SPACE, false, new DirectionAction(Direction.SPACE, false));
    }

    protected void addKeyBinding(String name, int keyEvent, boolean pressed, DirectionAction action) {
        addKeyBinding(name, KeyStroke.getKeyStroke(keyEvent, 0, !pressed), action);
    }

    protected void addKeyBinding(String name, KeyStroke keyStroke, DirectionAction action) {
        InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
        ActionMap actionMap = getActionMap();

        inputMap.put(keyStroke, name);
        actionMap.put(name, action);
    }

    @Override
    public void setController(GameController controller) {
        this.controller = controller;
    }

    @Override
    public GameController getController() {
        return controller;
    }

    @Override
    public Rectangle getViewBounds() {
        return new Rectangle(new Point(0, 0), getSize());
    }

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

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        GameController controller = getController();
        for (Entity entity : controller.getEntities()) {
            // I don't trust you
            Graphics2D g2d = (Graphics2D) g.create();
            entity.paint(g2d);
            g2d.dispose();
        }
    }

    public class DirectionAction extends AbstractAction {

        private Direction direction;
        private boolean pressed;

        public DirectionAction(Direction direction, boolean pressed) {
            this.direction = direction;
            this.pressed = pressed;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            getController().setDirection(direction, pressed);
        }

    }

}

好的,这很好,但是您如何使用它呢?例如这样的东西...

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
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 Main {

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

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

                GameModel model = new DefaultGameModel();
                DefaultGameView view = new DefaultGameView();
                GameController controller = new DefaultGameController(model, view);

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

                controller.start();
            }
        });
    }

    public static enum Direction {
        LEFT,
        RIGHT,
        SPACE
    }

    public interface Entity {
        public void paint(Graphics2D g2d);
        public Point getLocation();
        public void setLocation(Point p);
        public Dimension getSize();
    }

    public interface GameModel {
        public Entity[] getEntities();
        public void update(Rectangle bounds, Set<Direction> keys);
    }

    public interface GameController {
        public Entity[] getEntities();
        public void setDirection(Direction direction, boolean pressed);
        public void start();
    }

    public interface GameView {
        public void setController(GameController controller);
        public GameController getController();
        public Rectangle getViewBounds();
        public void repaint();
    }

    public class DefaultGameModel implements GameModel {

        private final List<Entity> entities;
        private Player player;

        private Long lastShot;

        public DefaultGameModel() {
            entities = new ArrayList<>(25);
        }

        @Override
        public Entity[] getEntities() {
            return entities.toArray(new Entity[0]);
        }

        @Override
        public void update(Rectangle bounds, Set<Direction> keys) {
            if (player == null) {
                player = new Player(bounds);
                entities.add(player);
            }

            Point p = player.getLocation();
            int xDelta = 0;
            if (keys.contains(Direction.LEFT)) {
                xDelta = -4;
            } else if (keys.contains(Direction.RIGHT)) {
                xDelta = 4;
            }
            p.x += xDelta;
            if (p.x <= bounds.x) {
                p.x = bounds.x;
            } else if (p.x + player.getSize().width >= bounds.x + bounds.width) {
                p.x = bounds.width - player.getSize().width;
            }
            player.setLocation(p);

            Iterator<Entity> it = entities.iterator();
            while (it.hasNext()) {
                Entity entity = it.next();
                if (entity instanceof Bullet) {
                    Point location = entity.getLocation();
                    Dimension size = entity.getSize();
                    location.y -= size.height;
                    if (location.y + size.height < bounds.y) {
                        it.remove();
                    } else {
                        entity.setLocation(location);
                    }
                }
            }

            if (keys.contains(Direction.SPACE)) {
                if (lastShot == null || System.currentTimeMillis() - lastShot > 100) {
                    lastShot = System.currentTimeMillis();
                    Bullet bullet = new Bullet();
                    int x = p.x + ((player.getSize().width - bullet.getSize().width) / 2);
                    int y = p.y - bullet.getSize().height;
                    bullet.setLocation(new Point(x, y));

                    entities.add(bullet);
                }
            }
        }

    }

    public class DefaultGameController implements GameController {

        private GameModel model;
        private GameView view;

        private Timer timer;

        private Set<Direction> keys = new HashSet<>(25);

        public DefaultGameController(GameModel gameModel, GameView gameView) {
            gameView.setController(this);

            view = gameView;
            model = gameModel;
        }

        @Override
        public Entity[] getEntities() {
            return model.getEntities();
        }

        @Override
        public void setDirection(Direction direction, boolean pressed) {
            if (pressed) {
                keys.add(direction);
            } else {
                keys.remove(direction);
            }
        }

        @Override
        public void start() {
            if (timer != null && timer.isRunning()) {
                timer.stop();
            }
            timer = new Timer(40, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    model.update(view.getViewBounds(), Collections.unmodifiableSet(keys));
                    view.repaint();
                }
            });
            timer.start();
        }

    }

    public abstract class AbstractEntity implements Entity {

        private final Point location = new Point(0, 0);

        @Override
        public Point getLocation() {
            return new Point(location);
        }

        @Override
        public void setLocation(Point p) {
            location.setLocation(p);
        }

    }

    public class Player extends AbstractEntity {

        public Player(Rectangle bounds) {
            int x = bounds.x + ((bounds.width - getSize().width) / 2);
            int y = bounds.y + (bounds.height - getSize().height);
            setLocation(new Point(x, y));
        }

        @Override
        public Dimension getSize() {
            return new Dimension(40, 17);
        }

        @Override
        public void paint(Graphics2D g2d) {
            Point p = getLocation();
            Dimension size = getSize();
            g2d.setColor(Color.BLUE);
            g2d.fillRect(p.x, p.y + 7, size.width, 10);
            g2d.fillRect(p.x + 18, p.y, 4, 7);
        }

    }

    public class Bullet extends AbstractEntity {

        @Override
        public void paint(Graphics2D g2d) {
            Rectangle bullet = new Rectangle(getLocation(), getSize());
            g2d.setColor(Color.RED);
            g2d.fill(bullet);
        }

        @Override
        public Dimension getSize() {
            return new Dimension(4, 8);
        }

    }

    public class DefaultGameView extends JPanel implements GameView {

        private GameController controller;

        public DefaultGameView() {
            addKeyBinding("left.pressed", KeyEvent.VK_LEFT, true, new DirectionAction(Direction.LEFT, true));
            addKeyBinding("left.released", KeyEvent.VK_LEFT, false, new DirectionAction(Direction.LEFT, false));
            addKeyBinding("right.pressed", KeyEvent.VK_RIGHT, true, new DirectionAction(Direction.RIGHT, true));
            addKeyBinding("right.released", KeyEvent.VK_RIGHT, false, new DirectionAction(Direction.RIGHT, false));
            addKeyBinding("space.pressed", KeyEvent.VK_SPACE, true, new DirectionAction(Direction.SPACE, true));
            addKeyBinding("space.released", KeyEvent.VK_SPACE, false, new DirectionAction(Direction.SPACE, false));
        }

        protected void addKeyBinding(String name, int keyEvent, boolean pressed, DirectionAction action) {
            addKeyBinding(name, KeyStroke.getKeyStroke(keyEvent, 0, !pressed), action);
        }

        protected void addKeyBinding(String name, KeyStroke keyStroke, DirectionAction action) {
            InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
            ActionMap actionMap = getActionMap();

            inputMap.put(keyStroke, name);
            actionMap.put(name, action);
        }

        @Override
        public void setController(GameController controller) {
            this.controller = controller;
        }

        @Override
        public GameController getController() {
            return controller;
        }

        @Override
        public Rectangle getViewBounds() {
            return new Rectangle(new Point(0, 0), getSize());
        }

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

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            GameController controller = getController();
            for (Entity entity : controller.getEntities()) {
                // I don't trust you
                Graphics2D g2d = (Graphics2D) g.create();
                entity.paint(g2d);
                g2d.dispose();
            }
        }

        public class DirectionAction extends AbstractAction {

            private Direction direction;
            private boolean pressed;

            public DirectionAction(Direction direction, boolean pressed) {
                this.direction = direction;
                this.pressed = pressed;
            }

            @Override
            public void actionPerformed(ActionEvent e) {
                getController().setDirection(direction, pressed);
            }

        }

    }

}

同样,您需要离开并做更多的研究,但这是一般的想法