问题描述
我曾尝试将侦听器添加到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) {
}
}
解决方法
-
永远不要直接在组件上调用paint(...)。如果您需要绘制组件,请对该组件调用 repaint()。 Swing 然后将paint(...) 该组件并在添加到面板的所有子组件上调用paint(...)。
-
所以这意味着您需要将 Snake 组件添加到父游戏面板。这也意味着没有理由覆盖Window面板的paint()方法。
-
不要使用静态变量和方法。这表明设计不佳。从变量和方法中删除 static 关键字。
-
对于动画,您应该使用 Swing Timer,而不是 Thread。 Swing 组件的更新应该在事件调度线程 (EDT) 上完成。计时器将在 EDT 上执行代码。
-
不要使用 KeyListener。 Swing 旨在与键绑定一起使用。请参阅:Motion Using the Keyboard 了解更多信息和示例。
-
自定义绘画是通过覆盖
paintComponent(...)
not paint(...) 来完成的。 -
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 部分移至我们的构造函数(见下文)。请注意,如果您想更新蛇的宽度和高度,则应使用不同的方法分别更新 parts
和 p1
和 p2
。
修复 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();