重绘不更新屏幕
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 行代码(不包括注释)的示例,解决了您代码中的以下问题:
删除 while(true)
语句以支持 Swing 计时器以防止阻塞事件调度线程 (EDT)
将程序放在 EDT 上,参见
的第 2 点
使用 JFrame#pack()
方法,而不是为 JFrame
和 JPanel
[ 手动设置 setSize(...)
=73=]
摆脱了"magic numbers"的方向,并使用enum
来处理这个问题,因此代码更多可读性,因为我们都知道 TOP
应该将它移到顶部,但我们不知道 1
应该将它移到顶部。
利用Shape
API在JPanel
上绘制形状,如图
我还建议使用 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
。
好了,一个简单的可运行示例,它是完整的、可验证的、最小的,这就是我们在以后的问题中对您的期望。
我想重新粉刷我的屏幕。到目前为止,它所做的只是在第一个屏幕上显示一个点,该点应该位于头部所在的位置。这很好,但是我在我的代码中写了我想每秒将头部向下移动 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 行代码(不包括注释)的示例,解决了您代码中的以下问题:
删除
while(true)
语句以支持 Swing 计时器以防止阻塞事件调度线程 (EDT)将程序放在 EDT 上,参见
的第 2 点
使用
[ 手动设置JFrame#pack()
方法,而不是为JFrame
和JPanel
setSize(...)
=73=]摆脱了"magic numbers"的方向,并使用
enum
来处理这个问题,因此代码更多可读性,因为我们都知道TOP
应该将它移到顶部,但我们不知道1
应该将它移到顶部。利用
Shape
API在JPanel
上绘制形状,如图我还建议使用
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
。
好了,一个简单的可运行示例,它是完整的、可验证的、最小的,这就是我们在以后的问题中对您的期望。