重绘不更新屏幕

Repaint does not update the screen

我想重新粉刷我的屏幕。到目前为止,它所做的只是在第一个屏幕上显示一个点,该点应该位于头部所在的位置。这很好,但是我在我的代码中写了我想每秒将头部向下移动 10 个像素。我正在打印头部应该在的位置,在命令提示符下它显示 y 值确实在增加。但是在我的屏幕上,头部没有移动。

我试过使用重新验证方法,试图扩展 canvas class 而不是 jframe,我试过使用不同的 classes 只是为了绘画方法,我有尝试用 paintComponent 方法替换 paint 方法。正如你可能会说的那样,我对 java 中与绘画相关的任何事情的理解都不够好。我已经尝试阅读这些 superclasses,但它们太复杂了,我无法理解。我也试过 运行 没有睡眠声明。这没关系。

主要class: class 包含开始 运行 贪吃蛇游戏的主要方法。

import java.util.concurrent.TimeUnit;

public class Main{

    public static void main(String[] args) throws InterruptedException {
        Main programma = new Main();
        programma.rungame();
    }

void rungame() throws InterruptedException {
        AllGUIElements gui = new AllGUIElements();
        gui.gui();
        while (true) {
            TimeUnit.SECONDS.sleep(1);
            gui.setGameScreen();
        }
    }
}

AllGUIElements class:此 class 创建一个包含新面板的新框架。该面板正在使用 paintComponent 绘制。 setGameScreen 更新头部的位置,应该重新绘制屏幕。

import javax.swing.*;
import java.awt.*;

public class AllGUIElements extends JPanel {

    private JFrame frame;
    private JPanel panel;

    private int screen;

    Snake hoofd = new Head(new Point(30,30),3,null);

    void gui() throws InterruptedException {

        frame = new JFrame("Snake Game");
        panel = new AllGUIElements();
        panel.setBackground(Color.GRAY);
        panel.setSize(1000,500);
        frame.setSize(1000,500);
        frame.add(panel);
        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

void setGameScreen() {
    repaint();
        if (hoofd.getDirection() == 1) {
            hoofd.setPosition(new Point(hoofd.getPosition().x, hoofd.getPosition().y-10));
        }
        if (hoofd.getDirection() == 2) {
            hoofd.setPosition(new Point(hoofd.getPosition().x+10, hoofd.getPosition().y));

        }
        if (hoofd.getDirection() == 3) {
            hoofd.setPosition(new Point(hoofd.getPosition().x, hoofd.getPosition().y+10));

        }
        if (hoofd.getDirection() == 4) {
            hoofd.setPosition(new Point(hoofd.getPosition().x-10, hoofd.getPosition().y));
        }
}

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
            g.setColor(Color.DARK_GRAY);
            g.fillRect(0, 0, 1000, 10);
            g.fillRect(0, 0, 10, 500);
            g.fillRect(990, 0, 10, 500);
            g.fillRect(0, 490, 1000, 10);

            g.setColor(Color.GREEN);
            g.fillRect(hoofd.getPosition().x, hoofd.getPosition().y, 10, 10);
    }

}

Screenobject class: Parent class of Snake and Food(没必要就不贴了)returns的位置头部、身体部位和食物。

import java.awt.*;

public class Screenobject{

    Point pos;

    Screenobject(Point pos){
        this.pos = pos;
    }

    void setPosition(Point pos){
        this.pos = pos;
    }

    Point getPosition() {
        return pos;
    }
}

蛇class:Child 的屏幕object 和parent 的头部和Body。 class 设置 object 的方向。

import java.awt.*;

public class Snake extends Screenobject{

    int direction;
    //directions:
    //1 = up
    //2 = right
    //3 = down
    //4 = left

    Snake nextpart;

    Snake(Point pos, int direction, Snake nextpart){
        super(pos);
        this.direction = direction;
        this.nextpart = nextpart;
    }

    int getDirection() {
        return direction;
    }
}

头class:蛇的Childclass,应该是蛇链表的第一个object。

import java.awt.*;

public class Head extends Snake{
    Head(Point pos, int direction, Snake nextpart){
        super(pos, direction, nextpart);
    }
}

Body class: Child class of Snake and grows for every food eaten.

import java.awt.*;

public class Body extends Snake{
    Body(Point pos, int direction, Snake nextpart){
        super(pos, direction, nextpart);
    }

}

要事第一:

I have used the "Divide and conquer" method to only post the necessary information

是的,您 post 只输入了必要的信息,但是您仍然为一个 MCVE / MRE 使用了太多 class,理想情况下,您的所有代码都可以放在一个class.

I didn't manage to make it any shorter without omitting code to make it run.

这就是我们要求 MRE 的想法,你应该创建一个全新的程序来隔离问题,在你的情况下是:在某个方向上移动形状并继续朝那个方向移动。

现在,我可以看到您已经尝试创建它并进行改进,所以我将 post 举个例子说明您以后提出的问题,以便您提出更好的问题未来。

话虽如此,让我们去回答你的问题。


这是一个不到 100 行代码(不包括注释)的示例,解决了您代码中的以下问题:

  1. 删除 while(true) 语句以支持 Swing 计时器以防止阻塞事件调度线程 (EDT)

  2. 将程序放在 EDT 上,参见

  3. 的第 2 点
  4. 使用 JFrame#pack() 方法,而不是为 JFrameJPanel

    [ 手动设置 setSize(...) =73=]
  5. 摆脱了"magic numbers"的方向,并使用enum来处理这个问题,因此代码更多可读性,因为我们都知道 TOP 应该将它移到顶部,但我们不知道 1 应该将它移到顶部。

  6. 利用Shape API在JPanel上绘制形状,如图

  7. 我还建议使用 camelCase 命名您的方法,以便 rungame() 变成 runGame() 因为它更容易阅读,其他方法也是如此。并给它们起更有意义的名字,例如 hoofd,我不知道那是什么,如果我在没有上下文的情况下单独阅读它,将很难说出它是什么类型的对象。


import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionListener;
import java.awt.geom.Rectangle2D;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class SnakeGame {
    private JFrame frame;
    private Snake snake;
    private JPanel buttonsPane;
    private JButton[] buttons; // Our array of buttons
    private Timer timer;
    private Direction currentDirection;

    // This enum will be used to determine the direction the snake will take.
    private enum Direction {
        TOP, LEFT, BOTTOM, RIGHT
    }

    public static void main(String[] args) {
        // We place our program on the EDT using Java 8 lambda expressions.
        SwingUtilities.invokeLater(() -> new SnakeGame().createAndShowGUI());
    }

    private void createAndShowGUI() {
        frame = new JFrame(getClass().getSimpleName());
        snake = new Snake();
        buttonsPane = new JPanel();
        buttons = new JButton[Direction.values().length];

        for (int i = 0; i < buttons.length; i++) {
            buttons[i] = new JButton(Direction.values()[i].toString()); // We create a JButton with the current value of the direction
            buttons[i].addActionListener(listener); // We set their ActionListeners
            buttonsPane.add(buttons[i]); // And add them to the buttonsPane
        }

        currentDirection = Direction.RIGHT; // We set a default direction
        timer = new Timer(1000, listener); // And start our Swing Timer

        // We add our components and then pack the frame, after that we start the timer.
        frame.add(snake);
        frame.add(buttonsPane, BorderLayout.SOUTH);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setVisible(true);
        timer.start();
    }

    // Our ActionListener for moving the snake
    private ActionListener listener = e -> { // Using Java 8 lambda expressions again
        // We set the current direction using a ternary, if the source of the event is
        // the timer we leave the current direction as is
        // otherwise we set it to the direction from the button clicked
        currentDirection = e.getSource().equals(timer) ? currentDirection : Direction.valueOf(e.getActionCommand());
        snake.move(currentDirection); // And we call the move method
    };

    @SuppressWarnings("serial")
    class Snake extends JPanel {
        private int xPos;
        private int yPos;
        private static final int SPEED = 10; // We set the speed as a constant (10 pixels at a time) in any direction

        // We determine the movement direction
        public void move(Direction direction) {
            switch (direction) {
            case TOP:
                yPos -= SPEED;
                break;
            case LEFT:
                xPos -= SPEED;
                break;
            case BOTTOM:
                yPos += SPEED;
                break;
            case RIGHT:
                xPos += SPEED;
                break;
            }
            this.repaint(); // And repaint the snake
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g;
            g2d.setColor(Color.DARK_GRAY);
            g2d.fill(new Rectangle2D.Double(xPos, yPos, 10, 10)); // We draw a rectangle using the Shape API
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(300, 300); // We set the preferredSize of the JPanel to 300 x 300
        }
    }
}

以上代码产生以下结果,对于 Java 7 及以下,或者如果您不想使用 lambda 表达式或对您来说太难,请检查此答案的第 2 点,它展示了如何在没有 lambda 表达式的情况下将程序放在 EDT 上, 展示了如何在没有它们的情况下编写 ActionListener

好了,一个简单的可运行示例,它是完整的、可验证的、最小的,这就是我们在以后的问题中对您的期望。