【问题标题】:I'm trying to write a snake game, but the KeyListener does not work我正在尝试编写蛇游戏,但 KeyListener 不起作用
【发布时间】:2021-07-03 17:25:19
【问题描述】:

我曾尝试将监听器添加到 Snake 类的对象、Window 类的对象和我在 Snake 类的构造函数中创建的 JFrame 类的对象,但它仍然不起作用。如果我按任何键,必须写入的字符和蛇必须转动,但它不会发生。 Сan是因为snakeThread吗?

public class Main {

    public static void main(String[] args) {
        Window window = new Window();
        System.out.print("k");
    }
}
import javax.swing.*;
import java.awt.*;

public class Window  extends JPanel {
    private Snake snake; 


    public Window() {
        super(true);
        snake = Snake.getSnake(50, 50);
        Thread snakeThread = new Thread(snake);
        snakeThread.start();
     

    }

    @Override
    public void paint(Graphics g) {
        super.paint(g);
        snake.paint(g);
    }
}
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;


public class Snake extends JPanel implements Runnable {

    public static int length;
    public static ArrayList<Point> parts;
    public static Field field;
    public static Food food;
    public static int speedX=0;
    public static int speedY=1;

private static Snake snake = null;

    public static final int TIME_DELTA = 1000;
public static  Snake getSnake(int w, int h)
{
    if(snake == null) {
        snake = new Snake(w, h);
        snake.addKeyListener(new Turn(snake));
    }
    return snake;
}

    private Snake(int Width, int Heigth) {

        JFrame jf = new JFrame();
        jf.setSize(500,500);
        jf.add(this); //this - это Jpanel которым расширяется Snake
        jf.setVisible(true);


        food = new Food();field = Field.getField();
        Point start = new Point((int)Width/2, (int)Heigth/2); //размеры поля, а не окна
        parts = new ArrayList<>();parts.add(start);
        Point p1 = new Point((int)start.getX(), ((int)start.getY())-1);parts.add(p1);
        Point p2 = new Point((int)start.getX(), ((int)p1.getY())-1);
        parts.add(p2);length = 3;
    }
  

    private boolean checkHead()
    {
        for (int i=1; i<parts.size(); ++i)
        {
            if(parts.get(parts.size()-1).getLocation() == parts.get(i).getLocation())
                return false;
        }

        if(parts.get(parts.size()-1).getX() <=0 || parts.get(parts.size()-1).getX() >= field.sizeX ||
                parts.get(parts.size()-1).getY() <=0 || parts.get(parts.size()-1).getY() >= field.sizeY )
            return false;

        return true;
    }

    public static void move()
    {
        for (Point i: parts)
        {
            i.y=i.y-1*speedY;
            i.x-=1*speedX;
        }
    }
    public static void eat()
    {
        Point np = new Point ((int)parts.get(length).getX(),(int)parts.get(length).getY()-1 );
        parts.add(np);
        ++length;
        food.respawn();
    }

    public static boolean checkFood()
    {
        if(parts.get(parts.size()-1).getX() == food.x &&  parts.get(parts.size()-1).getY()==food.y)
            return true;
        else
            return false;
    }

  
    @Override
    public void paint(Graphics g) {
        super.paint(g);
        for (Point i: parts) 
            g.fillRect((int) i.getX() * 10, (int) i.getY() * 10, 8, 8);

       g.setColor(Color.RED);
       g.fillRect(food.x * 10, food.y * 10, 8, 8);
       g.setColor(Color.BLACK);
    }

    @Override

            public void run() {
        while (checkHead()) {
            move();
            repaint();
            if(checkFood())
                eat();
            try {
                Thread.sleep(TIME_DELTA);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
            }

            public  void turn(char Key)
            {
                int delta = 0;
                for(Point i: parts)
                {
                    switch (Key) {
                        case 'a':
                            i.y=parts.get(parts.size()-1).y;
                            i.x=parts.get(parts.size()-1).x+delta;
                            break;
                        case'd':
                            i.y=parts.get(parts.size()-1).y;
                            i.x=parts.get(parts.size()-1).x-delta;
                            break;

                        case 'w':
                            i.x=parts.get(parts.size()-1).x;
                            i.y=parts.get(parts.size()-1).y-delta;
                            break;
                        case's':
                            i.x=parts.get(parts.size()-1).x;
                            i.y=parts.get(parts.size()-1).y+delta;
                            break;
                    }
                    ++delta;
                }
                repaint();
            }


}
import javax.swing.*;
import java.awt.*;
import java.util.Random;

public class Food extends JPanel {
    public static  int x;
    public static int y;
    private static Random random;

    public Food()
    {
        super(true);
        random = new Random();
        
       x =  random.nextInt(50);
       y = random.nextInt(50);
    }

    @Override
   public void paint(Graphics g) {
        super.paint(g);
        g.setColor(Color.RED);
        g.fillRect(x * 10, y * 10, 8, 8);
        g.setColor(Color.BLACK);
    }

    public  void respawn()
    {
        x = random.nextInt(40);
        y = random.nextInt(40);
        repaint();
    }
}

听众在这里:

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;


public class Turn implements KeyListener {
   private char key = 'O';
   private Snake snake;

   public Turn(Snake s)
   {
       this.snake = s;
   }


    @Override
    public void keyTyped(KeyEvent e) {
        System.out.println("0");//when I press a button, "0" must be written, but it doesn't
        if(e.getKeyChar() == 'A')
        {
            System.out.println("a");//this character too
            if(snake.speedX==0)
            {
                snake.speedX=-1;//speedX was not changed
                snake.speedY=0;
                key='a';
            }
        }
        else if (e.getKeyChar() == 'W')
        {
            System.out.println("w");
            if(snake.speedY==0)
            {
                snake.speedY=-1;
                snake.speedX=0;
                key='w';
            }

        }
        else if (e.getKeyChar() == 'S')
        {
            System.out.println("s");
            if(snake.speedY==0)
            {
                snake.speedY=1;
                snake.speedX=0;
                key='s';
            }

        }
        else if (e.getKeyChar() == 'D')
        {
            System.out.println("d");
            if(snake.speedX==0)
            {
                snake.speedX=1;
                snake.speedY=0;
                key='d';
            }
        }
        if(key!='O')
snake.turn(key);
    }

    @Override
    public void keyPressed(KeyEvent e) {

    }

    @Override
    public void keyReleased(KeyEvent e) {

    }
}

【问题讨论】:

    标签: java multithreading listener keylistener game-development


    【解决方案1】:
    1. 永远不要直接在组件上调用paint(...)。如果您需要绘制组件,请在该组件上调用 repaint()。然后 Swing 将绘制(...)该组件并在添加到面板的所有子组件上调用 paint(...)。

    2. 所以这意味着您需要将 Snake 组件添加到父游戏面板。这也意味着没有理由重写 Window 面板的 paint() 方法。

    3. 不要使用静态变量和方法。这表明设计不佳。从变量和方法中删除 static 关键字。

    4. 对于动画,您应该使用 Swing Timer,而不是线程。 Swing 组件的更新应该在事件调度线程 (EDT) 上完成。 Timer 将在 EDT 上执行代码。

    5. 不要使用 KeyListener。 Swing 被设计为与键绑定一起使用。有关更多信息和示例,请参阅:Motion Using the Keyboard

    6. 自定义绘画是通过覆盖paintComponent(...) not paint(...) 来完成的。

    7. main() 方法的重点是创建框架并将游戏面板添加到框架中。游戏面板不应创建框架。

    【讨论】:

      【解决方案2】:

      我同意 camickr 回答中的所有内容。您的代码中有很多问题,从头开始考虑这些问题将有助于避免您面临的许多问题。

      但是,您的实际问题是这一行 private static Snake snake = null; 和这个方法:

      public static  Snake getSnake(int w, int h)
      {
          if(snake == null) {
              snake = new Snake(w, h);
              snake.addKeyListener(new Turn(snake));
          }
          return snake;
      }
      

      您永远不需要在这样的类中创建自引用。 Snake 类已经是一个Snake 对象,因此在类中使用private static Snake snake = null; 只会在Snake 中创建一个对全新且不同的Snake 的新引用。相反,您需要使用this 关键字来引用Snake 对象,例如this.addKeyListener(new Turn(this)); 而不是snake.addKeyListener(new Turn(snake));

      删除/删除getSnake 方法,您正在尝试混合静态和非静态对象并且不需要它,我们可以将方法的keylistener 部分移动到我们的构造函数(见下文)。请注意,如果您想更新蛇的宽度和高度,那么您应该使用不同的方法分别更新 partsp1p2

      修复 Snake 类:

      固定的 Snake 类可能如下所示:

      public class Snake extends JPanel implements Runnable {
      
          //None of the variables or methods should be static
          //remove the static keyword from everywhere in the Snake class
          public int length;
          public ArrayList<Point> parts;
          public Field field;
          public Food food;
          public int speedX=0;
          public int speedY=1;
      
          public final int TIME_DELTA = 1000;
      
          //The constructor needs to be a public method (Not private)
          public Snake(int Width, int Heigth) {
              //Delete all the JFrame code,
              //it should be done in the main method
      
              //Add the key ilstener here (moved from the getSnake method)
              this.addKeyListener(new Turn(this));
              
              //Create the snake
              food = new Food();
              field = Field.getField();
              Point start = new Point((int)Width/2, (int)Heigth/2); //размеры поля, а не окна
              parts = new ArrayList<>();
              parts.add(start);
              Point p1 = new Point((int)start.getX(), ((int)start.getY())-1);parts.add(p1);
              Point p2 = new Point((int)start.getX(), ((int)p1.getY())-1);
              parts.add(p2);
              length = 3;
          }
      
          //Rest of the code removed for clarity
          ...
      }
      

      修复main方法,删除Window类:

      要使用这个更新的 Snake 类,您可以完全删除 Window 类并在主类中使用类似这样的内容:

      public class Main {
      
          public static void main(String[] args) {
              //Create JFrame
              JFrame jf = new JFrame();
              jf.setSize(500,500);
      
              //Create Snake
              Snake snake = new Snake(50, 50);
      
              //Add Snake to JFrame and set the frame visible
              jf.add(snake);
              jf.setVisible(true);
              
              //Finally start the Snake thread
              Thread snakeThread = new Thread(snake);
              snakeThread.start();
          }
      }
      

      还有很多问题需要修复,但这应该可以解决您的关键监听器无法正常工作的问题。


      其他修复:

      除了键监听器,更好的解决方案是使用键绑定。我们可以在你的 main 方法中创建一个这样的键绑定:

      //Key binding to trigger when KeyEvent.VK_W ('w') is pressed
      snake.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0), "NORTH");
      snake.getActionMap().put("NORTH", new AbstractAction()
           {
           @Override
           public void actionPerformed(ActionEvent e)
           {
               //Your code here to change the snake direction
               //we can call the turn method from inside this snake object/class
               snake.turn('w');
           }
           });
      

      您应该在 Snake 类中覆盖 protected void paintComponent(Graphics g) 而不是 public void paint(Graphics g),如下所示:

      @Override
      protected void paintComponent(Graphics g){
          super.paintComponent(g);
          Graphics2D g2d = (Graphics2D) g;
          for (Point i: parts) 
              g2d.fillRect((int) i.getX() * 10, (int) i.getY() * 10, 8, 8);
      
         g2d.setColor(Color.RED);
         g2d.fillRect(food.x * 10, food.y * 10, 8, 8);
         g2d.setColor(Color.BLACK);
      }
      

      最后,不要让你的Snake 类实现Runniable,你应该在你的Snake 类中创建一个类似这样的简单的独立摇摆计时器:

      void startTimer(){
          //Start timer to update snake location every 100ms
          Timer timer = new Timer(100, new ActionListener(){
              @Override
              public void actionPerformed(ActionEvent ae){
                  //Your code here to update and repaint the snake location
                  snake.updateLocation();
              }
          });
          timer.start();
      }
      

      在您的主要方法中,您可以简单地使用snake.startTimer(); 而不是Thread snakeThread = new Thread(snake);snakeThread.start();

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-07-08
        • 1970-01-01
        • 2018-07-20
        • 2021-12-31
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多