微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

我正在尝试编写蛇游戏,但 KeyListener 不起作用

如何解决我正在尝试编写蛇游戏,但 KeyListener 不起作用

我曾尝试将侦听器添加到Snake类的对象中,添加到我在Snake类的构造函数中创建的Window类的对象和JFrame类的对象中,但它仍然不起作用。如果我按任意键,必须写入的字符和蛇必须转动,但它不会发生。是不是因为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);
       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);
        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) {

    }
}

解决方法

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

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

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

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

  5. 不要使用 KeyListener。 Swing 旨在与键绑定一起使用。请参阅:Motion Using the Keyboard 了解更多信息和示例。

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

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

,

我同意 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);
   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();

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。