蛇与自身碰撞的问题(贪吃蛇游戏)
Problem with snake colliding with itself (snake game)
我的贪吃蛇游戏遇到了这个问题,例如,当您向左移动并快速向上和向右按时,它 运行 会自行解决,因为我只告诉游戏我不能如果它向左移动,请按右,因此,如果我在按右之前按右,它允许我让蛇进入自身。
因此,当您 运行 程序时,只需按 Next 并按 space 游戏就会开始,然后当您向左移动时,只需快速按向上和向右按钮,然后您就可以看到它了。不幸的是,我不确定如何解决这个问题,因为我们只学习了 Java 大约 6 个月,而且我们只真正学习了 if 等基础知识。如果您有任何问题,我会快速回答。
这不是正确的解决方案吗?
如果有人转得太快,比如向左行驶,'up' 和 'right' 太快,蛇仍然在同一个 y 轴上,因为它还没有改变向上移动,并按下 'right' 键,蛇试图在 x 轴上移动,而 y 轴值仍然相同。
我在您的程序中捕获了 x 和 y 值,对于正常的 运行,它们在第一次转向向下时看起来像这样。开始后,我让蛇运行一直到右边的墙,在它撞到墙之前掉头:
x[0],y[0] | x[1],y[1]
720 , 0 | 680, 0 <-- coordinates before turn
720 , 40 | 720, 0 <-- coordinates after turn
这个,开始后,我让蛇运行翻墙,然后快速向下+向右转,导致游戏结束,坐标如下:
x[0],y[0] | x[1],y[1]
720 , 0 | 680, 0 <-- coordinates before turn
680 , 0 | 720, 0 <-- coordinates after down + right turn
在我看来,行为应该是这样的,除非我遗漏了什么。如果您想避免这种情况,请尝试在按键后添加一小部分延迟。
我对您的代码进行了一些更改,现在它可以像您期望的那样正常工作。
本质上的问题是,如果在计时器滴答和调用 snakeMove
之前足够快地按下 2 个键,您将覆盖 direction
变量,因此将“丢失”一个键。
想象一下发生了这些步骤:
direction
是V,蛇向左走
- 计时器计时并调用
snakeMove
,该方法计算 direction
,即 V,因此蛇继续向左移动
- 在计时器再次计时之前,我“同时”按下 向上 + 向右。因此,在计时器再次计时之前发生了 2 个事件:
- 第一个密钥已处理,因此
direction
设置为 up direction == "U"
- 第二个键被处理所以
direction
被设置为 right direction == "H"
- 现在只有计时器再次计时并调用
snakeMove
。该方法在 switch 语句中评估 direction
和 direction == "H"
,因此我们“错过”了 direction == "U"
,因为它被 keyPressed
方法中的第二次按键覆盖计时器已计时
为了克服这个问题,正如我在你之前的问题中所建议的那样,使用 FIFO(先进先出)列表来正确处理所有密钥,这样它们就不会“丢失”。
这可以使用 LinkedList 来完成,它具有我们需要的 pop()
功能。
所以在你的代码中我将全局变量 direction
重命名为 currentDirection
:
private String currentDirection;
并删除了 static
修饰符,因为这不是必需的,我还删除了 snakeMove
上的 static
修饰符,因为这也是不需要的,并且阻止我们访问实例变量,即currentDirection
。我还将范围更改为 private
,因为在您显示的代码段中没有必要将其设置为 public
,但这些更改只是为了提高代码的正确性。然后我创建了一个全局变量:
private LinkedList<String> directions = new LinkedList<>();
然后到处(除了snakeMove
方法)我删除了currentDirection =
并用directions.add(...)
替换它,因此我们不再改变单个变量,而是添加每个方向到我们的 FIFO/LinkedList
。我还删除了你在 keyPressed
中所做的一些 if 检查,因为这不是必需的,即使蛇与按下的键的方向相同 - 谁在乎,只需将它添加到按下的键列表中所以我们稍后可以在 snakeMove
中处理它
然后在你的 snakeMove
我这样做了:
public void snakeMove() {
...
if (!directions.isEmpty()) { // if its not empty we have a new key(s) to process
// proccess the keys pressed from the oldest to the newest and set the new direction
currentDirection = directions.pop(); // takes the first oldest key from the queue
}
switch (currentDirection) {
...
}
}
这样就解决了上面提到的问题。
以下是实施了这些更改的代码:
import javax.sound.sampled.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.File;
import java.util.LinkedList;
public class FirstGraphic extends JPanel implements ActionListener, KeyListener {
//Storlek på fönstret
static int WIDTH = 800, HEIGHT = 840;
Timer tmMove = new Timer(150, this);
private JFrame window;
static int bodySize = 40, xNormalFruit = 0, yNormalFruit = 0, gameSquares = (WIDTH * HEIGHT) / bodySize,
snakeParts = 7, score = 0, restartButtonWIDTH = 190, restartButtonHEIGHT = 50;
static int x[] = new int[gameSquares];
static int y[] = new int[gameSquares];
private String currentDirection;
boolean gameRunning = false, gameStarted = false, instructions = false, isDead = false;
public static JButton restartButton = new JButton("STARTA OM"), toInstructionsButton = new JButton("Nästa");
private LinkedList<String> directions = new LinkedList<>();
public static void main(String[] args) {
JFrame window = new JFrame("Snake Game");
FirstGraphic content = new FirstGraphic(window);
window.setContentPane(content);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setResizable(false);
window.pack();
restartButton.setBounds((WIDTH / 2) - (restartButtonWIDTH) / 2, (HEIGHT - 40) / 2 + 100, restartButtonWIDTH, restartButtonHEIGHT);
restartButton.setBackground(new Color(48, 165, 55));
restartButton.setFont(new Font("Arial", Font.BOLD, 20));
window.setLocationRelativeTo(null);
window.setVisible(true);
content.setUp();
}
public FirstGraphic(JFrame window) {
super();
setPreferredSize(new Dimension(WIDTH, HEIGHT));
setFocusable(true);
requestFocus();
this.window = window;
}
public void setUp() {
addKeyListener(this);
setFocusable(true);
setFocusTraversalKeysEnabled(false);
restartButton.addActionListener(this);
directions.add("H");
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (gameRunning) {
g.setColor(new Color(0, 0, 0));
g.fillRect(0, 0, WIDTH, (HEIGHT - 40));
g.setColor(new Color(63, 116, 41, 255));
g.fillRect(0, HEIGHT - 40, WIDTH, (2));
g.setColor(new Color(0, 0, 0, 240));
g.fillRect(0, HEIGHT - 38, WIDTH, 38);
g.setColor(new Color(0, 0, 0));
}
draw(g);
}
public void draw(Graphics g) {
if (gameRunning) {
g.setColor(new Color(35, 179, 52, 223));
g.fillOval(xNormalFruit, yNormalFruit, bodySize, bodySize);
g.setColor(new Color(44, 141, 23, 255));
g.setFont(new Font("Arial", Font.BOLD, 18));
for (int i = 0; i < snakeParts; i++) {
if (i == 0) {
g.setColor(Color.RED);
g.fillOval(x[i], y[i], bodySize, bodySize);
} else {
g.setColor(Color.PINK);
g.fillOval(x[i], y[i], bodySize, bodySize);
}
}
} else if (!gameRunning && gameStarted) {
gameOver(g);
} else if (!instructions) {
startScene(g);
} else {
instructions(g);
}
}
public void startScene(Graphics g) {
g.setColor(Color.BLACK);
g.fillRect(0, 0, WIDTH, HEIGHT);
g.setColor(Color.WHITE);
g.setFont(new Font("Arial", Font.BOLD, 85));
g.drawString("Ormen Olle's", 150, 170);
g.drawString("Äventyr", 235, 254);
window.add(toInstructionsButton);
toInstructionsButton.setBounds(240, 660, 300, 100);
toInstructionsButton.setBackground(new Color(48, 165, 55));
toInstructionsButton.setForeground(Color.BLACK);
toInstructionsButton.setFont(new Font("Arial", Font.BOLD, 60));
toInstructionsButton.addActionListener(this);
}
public void instructions(Graphics g) {
g.setFont(new Font("Arial", Font.BOLD, 85));
g.setColor(new Color(14, 69, 114));
g.drawString("PRESS SPACE", 210, 720);
}
public void gameOver(Graphics g) {
g.setColor(Color.BLACK);
g.fillRect(0, 0, WIDTH, HEIGHT);
g.setColor(Color.red);
g.setFont(new Font("Arial", Font.BOLD, 65));
FontMetrics metrics = getFontMetrics(g.getFont());
g.drawString("Du dog!", (WIDTH - metrics.stringWidth("Du dog!")) / 2, (HEIGHT - 40) / 2);
g.setColor(new Color(44, 141, 23, 255));
g.setFont(new Font("Arial", Font.BOLD, 20));
FontMetrics metrics2 = getFontMetrics(g.getFont());
g.drawString("SCORE: " + score, (WIDTH - metrics2.stringWidth("SCORE: " + score)) / 2, 50);
window.add(restartButton);
}
public void checkFruit() {
if ((x[0] == xNormalFruit) && (y[0] == yNormalFruit)) {
snakeParts++;
score++;
newFruit();
}
for (int v = 1; v < snakeParts; v++) {
if ((x[v] == xNormalFruit) && y[v] == yNormalFruit) {
newFruit();
}
}
}
public void checkCollisions() {
for (int i = snakeParts; i > 0; i--) {
if ((x[0] == x[i]) && (y[0] == y[i])) {
gameRunning = false;
isDead = true;
}
}
if (x[0] < 0) {
gameRunning = false;
isDead = true;
}
if (x[0] == WIDTH) {
gameRunning = false;
isDead = true;
}
if (y[0] < 0) {
gameRunning = false;
isDead = true;
}
if (y[0] > (HEIGHT - 40) - bodySize) {
gameRunning = false;
isDead = true;
}
if (!gameRunning) {
tmMove.stop();
}
}
public void snakeMove() {
for (int i = snakeParts; i > 0; i--) {
x[i] = x[i - 1];
y[i] = y[i - 1];
}
if (!directions.isEmpty()) {
currentDirection = directions.pop();
}
switch (currentDirection) {
case "H":
x[0] = x[0] + bodySize;
break;
case "V":
x[0] = x[0] - bodySize;
break;
case "U":
y[0] = y[0] - bodySize;
break;
case "N":
y[0] = y[0] + bodySize;
break;
}
}
public static void newFruit() {
xNormalFruit = (rollDice(WIDTH / bodySize) * bodySize) - bodySize;
yNormalFruit = (rollDice((HEIGHT - 40) / bodySize) * bodySize) - bodySize;
}
public static int rollDice(int numberOfSides) {
//Kastar en tärning med ett specifikt antal sidor.
return (int) (Math.random() * numberOfSides + 1);
}
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (actionEvent.getSource() == restartButton && isDead) {
isDead = false;
for (int i = 0; i < snakeParts; i++) {
if (i == 0) {
x[i] = 0;
y[i] = 0;
} else {
x[i] = 0 - bodySize;
y[i] = 0;
}
}
gameRunning = true;
tmMove.start();
//direction = "H";
directions.clear();
directions.add("H");
window.remove(restartButton);
score = 0;
snakeParts = 7;
newFruit();
repaint();
}
if (actionEvent.getSource() == toInstructionsButton && !instructions) {
instructions = true;
window.remove(toInstructionsButton);
repaint();
}
if (actionEvent.getSource() == tmMove) {
if (gameRunning) {
snakeMove();
checkFruit();
checkCollisions();
} else {
repaint();
}
repaint();
}
}
@Override
public void keyTyped(KeyEvent ke) {
}
@Override
public void keyPressed(KeyEvent ke) {
if (ke.getKeyCode() == KeyEvent.VK_SPACE && !gameRunning && instructions) {
snakeMove();
checkFruit();
checkCollisions();
newFruit();
gameRunning = true;
instructions = false;
}
if (ke.getKeyCode() == KeyEvent.VK_SPACE && gameRunning) {
if (gameStarted) {
gameStarted = false;
tmMove.stop();
} else {
tmMove.start();
gameStarted = true;
}
}
if (gameStarted) {
switch (ke.getKeyCode()) {
case KeyEvent.VK_RIGHT:
case KeyEvent.VK_D:
directions.add("H");
break;
case KeyEvent.VK_LEFT:
case KeyEvent.VK_A:
directions.add("V");
break;
case KeyEvent.VK_UP:
case KeyEvent.VK_W:
directions.add("U");
break;
case KeyEvent.VK_DOWN:
case KeyEvent.VK_S:
directions.add("N");
break;
case KeyEvent.VK_E:
tmMove.setDelay(200);
break;
case KeyEvent.VK_M:
tmMove.setDelay(150);
break;
case KeyEvent.VK_H:
tmMove.setDelay(100);
break;
}
}
}
@Override
public void keyReleased(KeyEvent ke) {
}
}
其他一些注意事项如下:
- 应通过
SwingUtilities.invokeLater
在 EDT 上创建所有 swing 组件
- 不要使用
setBounds
或 setSize
,使用适当的 layout manager 并在添加所有组件之后并使其可见之前调用 JFrame#pack()
- 覆盖
getPreferredSize
而不是调用 setPreferredSize
- 您不需要使用图形来完成简单的事情,例如绘制字符串,只需使用
JLabel
并将其添加到具有适当布局的 JPanel
- 您可以将
Graphics
对象投射到 Graphics2D
对象,并将一些 anti-aliasing and other rendering hints 投射到您的绘图中,使其看起来更清晰
- 您应该使用 KeyBindings 而不是
KeyListener
- 分离您的代码问题,不要将一个
actionPerformed
用于所有目的,例如计时器和按钮回调,使用任意 类 以使代码更简洁
我的贪吃蛇游戏遇到了这个问题,例如,当您向左移动并快速向上和向右按时,它 运行 会自行解决,因为我只告诉游戏我不能如果它向左移动,请按右,因此,如果我在按右之前按右,它允许我让蛇进入自身。
因此,当您 运行 程序时,只需按 Next 并按 space 游戏就会开始,然后当您向左移动时,只需快速按向上和向右按钮,然后您就可以看到它了。不幸的是,我不确定如何解决这个问题,因为我们只学习了 Java 大约 6 个月,而且我们只真正学习了 if 等基础知识。如果您有任何问题,我会快速回答。
这不是正确的解决方案吗?
如果有人转得太快,比如向左行驶,'up' 和 'right' 太快,蛇仍然在同一个 y 轴上,因为它还没有改变向上移动,并按下 'right' 键,蛇试图在 x 轴上移动,而 y 轴值仍然相同。
我在您的程序中捕获了 x 和 y 值,对于正常的 运行,它们在第一次转向向下时看起来像这样。开始后,我让蛇运行一直到右边的墙,在它撞到墙之前掉头:
x[0],y[0] | x[1],y[1]
720 , 0 | 680, 0 <-- coordinates before turn
720 , 40 | 720, 0 <-- coordinates after turn
这个,开始后,我让蛇运行翻墙,然后快速向下+向右转,导致游戏结束,坐标如下:
x[0],y[0] | x[1],y[1]
720 , 0 | 680, 0 <-- coordinates before turn
680 , 0 | 720, 0 <-- coordinates after down + right turn
在我看来,行为应该是这样的,除非我遗漏了什么。如果您想避免这种情况,请尝试在按键后添加一小部分延迟。
我对您的代码进行了一些更改,现在它可以像您期望的那样正常工作。
本质上的问题是,如果在计时器滴答和调用 snakeMove
之前足够快地按下 2 个键,您将覆盖 direction
变量,因此将“丢失”一个键。
想象一下发生了这些步骤:
direction
是V,蛇向左走- 计时器计时并调用
snakeMove
,该方法计算direction
,即 V,因此蛇继续向左移动 - 在计时器再次计时之前,我“同时”按下 向上 + 向右。因此,在计时器再次计时之前发生了 2 个事件:
- 第一个密钥已处理,因此
direction
设置为 updirection == "U"
- 第二个键被处理所以
direction
被设置为 rightdirection == "H"
- 第一个密钥已处理,因此
- 现在只有计时器再次计时并调用
snakeMove
。该方法在 switch 语句中评估direction
和direction == "H"
,因此我们“错过”了direction == "U"
,因为它被keyPressed
方法中的第二次按键覆盖计时器已计时
为了克服这个问题,正如我在你之前的问题中所建议的那样,使用 FIFO(先进先出)列表来正确处理所有密钥,这样它们就不会“丢失”。
这可以使用 LinkedList 来完成,它具有我们需要的 pop()
功能。
所以在你的代码中我将全局变量 direction
重命名为 currentDirection
:
private String currentDirection;
并删除了 static
修饰符,因为这不是必需的,我还删除了 snakeMove
上的 static
修饰符,因为这也是不需要的,并且阻止我们访问实例变量,即currentDirection
。我还将范围更改为 private
,因为在您显示的代码段中没有必要将其设置为 public
,但这些更改只是为了提高代码的正确性。然后我创建了一个全局变量:
private LinkedList<String> directions = new LinkedList<>();
然后到处(除了snakeMove
方法)我删除了currentDirection =
并用directions.add(...)
替换它,因此我们不再改变单个变量,而是添加每个方向到我们的 FIFO/LinkedList
。我还删除了你在 keyPressed
中所做的一些 if 检查,因为这不是必需的,即使蛇与按下的键的方向相同 - 谁在乎,只需将它添加到按下的键列表中所以我们稍后可以在 snakeMove
然后在你的 snakeMove
我这样做了:
public void snakeMove() {
...
if (!directions.isEmpty()) { // if its not empty we have a new key(s) to process
// proccess the keys pressed from the oldest to the newest and set the new direction
currentDirection = directions.pop(); // takes the first oldest key from the queue
}
switch (currentDirection) {
...
}
}
这样就解决了上面提到的问题。
以下是实施了这些更改的代码:
import javax.sound.sampled.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.File;
import java.util.LinkedList;
public class FirstGraphic extends JPanel implements ActionListener, KeyListener {
//Storlek på fönstret
static int WIDTH = 800, HEIGHT = 840;
Timer tmMove = new Timer(150, this);
private JFrame window;
static int bodySize = 40, xNormalFruit = 0, yNormalFruit = 0, gameSquares = (WIDTH * HEIGHT) / bodySize,
snakeParts = 7, score = 0, restartButtonWIDTH = 190, restartButtonHEIGHT = 50;
static int x[] = new int[gameSquares];
static int y[] = new int[gameSquares];
private String currentDirection;
boolean gameRunning = false, gameStarted = false, instructions = false, isDead = false;
public static JButton restartButton = new JButton("STARTA OM"), toInstructionsButton = new JButton("Nästa");
private LinkedList<String> directions = new LinkedList<>();
public static void main(String[] args) {
JFrame window = new JFrame("Snake Game");
FirstGraphic content = new FirstGraphic(window);
window.setContentPane(content);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setResizable(false);
window.pack();
restartButton.setBounds((WIDTH / 2) - (restartButtonWIDTH) / 2, (HEIGHT - 40) / 2 + 100, restartButtonWIDTH, restartButtonHEIGHT);
restartButton.setBackground(new Color(48, 165, 55));
restartButton.setFont(new Font("Arial", Font.BOLD, 20));
window.setLocationRelativeTo(null);
window.setVisible(true);
content.setUp();
}
public FirstGraphic(JFrame window) {
super();
setPreferredSize(new Dimension(WIDTH, HEIGHT));
setFocusable(true);
requestFocus();
this.window = window;
}
public void setUp() {
addKeyListener(this);
setFocusable(true);
setFocusTraversalKeysEnabled(false);
restartButton.addActionListener(this);
directions.add("H");
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (gameRunning) {
g.setColor(new Color(0, 0, 0));
g.fillRect(0, 0, WIDTH, (HEIGHT - 40));
g.setColor(new Color(63, 116, 41, 255));
g.fillRect(0, HEIGHT - 40, WIDTH, (2));
g.setColor(new Color(0, 0, 0, 240));
g.fillRect(0, HEIGHT - 38, WIDTH, 38);
g.setColor(new Color(0, 0, 0));
}
draw(g);
}
public void draw(Graphics g) {
if (gameRunning) {
g.setColor(new Color(35, 179, 52, 223));
g.fillOval(xNormalFruit, yNormalFruit, bodySize, bodySize);
g.setColor(new Color(44, 141, 23, 255));
g.setFont(new Font("Arial", Font.BOLD, 18));
for (int i = 0; i < snakeParts; i++) {
if (i == 0) {
g.setColor(Color.RED);
g.fillOval(x[i], y[i], bodySize, bodySize);
} else {
g.setColor(Color.PINK);
g.fillOval(x[i], y[i], bodySize, bodySize);
}
}
} else if (!gameRunning && gameStarted) {
gameOver(g);
} else if (!instructions) {
startScene(g);
} else {
instructions(g);
}
}
public void startScene(Graphics g) {
g.setColor(Color.BLACK);
g.fillRect(0, 0, WIDTH, HEIGHT);
g.setColor(Color.WHITE);
g.setFont(new Font("Arial", Font.BOLD, 85));
g.drawString("Ormen Olle's", 150, 170);
g.drawString("Äventyr", 235, 254);
window.add(toInstructionsButton);
toInstructionsButton.setBounds(240, 660, 300, 100);
toInstructionsButton.setBackground(new Color(48, 165, 55));
toInstructionsButton.setForeground(Color.BLACK);
toInstructionsButton.setFont(new Font("Arial", Font.BOLD, 60));
toInstructionsButton.addActionListener(this);
}
public void instructions(Graphics g) {
g.setFont(new Font("Arial", Font.BOLD, 85));
g.setColor(new Color(14, 69, 114));
g.drawString("PRESS SPACE", 210, 720);
}
public void gameOver(Graphics g) {
g.setColor(Color.BLACK);
g.fillRect(0, 0, WIDTH, HEIGHT);
g.setColor(Color.red);
g.setFont(new Font("Arial", Font.BOLD, 65));
FontMetrics metrics = getFontMetrics(g.getFont());
g.drawString("Du dog!", (WIDTH - metrics.stringWidth("Du dog!")) / 2, (HEIGHT - 40) / 2);
g.setColor(new Color(44, 141, 23, 255));
g.setFont(new Font("Arial", Font.BOLD, 20));
FontMetrics metrics2 = getFontMetrics(g.getFont());
g.drawString("SCORE: " + score, (WIDTH - metrics2.stringWidth("SCORE: " + score)) / 2, 50);
window.add(restartButton);
}
public void checkFruit() {
if ((x[0] == xNormalFruit) && (y[0] == yNormalFruit)) {
snakeParts++;
score++;
newFruit();
}
for (int v = 1; v < snakeParts; v++) {
if ((x[v] == xNormalFruit) && y[v] == yNormalFruit) {
newFruit();
}
}
}
public void checkCollisions() {
for (int i = snakeParts; i > 0; i--) {
if ((x[0] == x[i]) && (y[0] == y[i])) {
gameRunning = false;
isDead = true;
}
}
if (x[0] < 0) {
gameRunning = false;
isDead = true;
}
if (x[0] == WIDTH) {
gameRunning = false;
isDead = true;
}
if (y[0] < 0) {
gameRunning = false;
isDead = true;
}
if (y[0] > (HEIGHT - 40) - bodySize) {
gameRunning = false;
isDead = true;
}
if (!gameRunning) {
tmMove.stop();
}
}
public void snakeMove() {
for (int i = snakeParts; i > 0; i--) {
x[i] = x[i - 1];
y[i] = y[i - 1];
}
if (!directions.isEmpty()) {
currentDirection = directions.pop();
}
switch (currentDirection) {
case "H":
x[0] = x[0] + bodySize;
break;
case "V":
x[0] = x[0] - bodySize;
break;
case "U":
y[0] = y[0] - bodySize;
break;
case "N":
y[0] = y[0] + bodySize;
break;
}
}
public static void newFruit() {
xNormalFruit = (rollDice(WIDTH / bodySize) * bodySize) - bodySize;
yNormalFruit = (rollDice((HEIGHT - 40) / bodySize) * bodySize) - bodySize;
}
public static int rollDice(int numberOfSides) {
//Kastar en tärning med ett specifikt antal sidor.
return (int) (Math.random() * numberOfSides + 1);
}
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (actionEvent.getSource() == restartButton && isDead) {
isDead = false;
for (int i = 0; i < snakeParts; i++) {
if (i == 0) {
x[i] = 0;
y[i] = 0;
} else {
x[i] = 0 - bodySize;
y[i] = 0;
}
}
gameRunning = true;
tmMove.start();
//direction = "H";
directions.clear();
directions.add("H");
window.remove(restartButton);
score = 0;
snakeParts = 7;
newFruit();
repaint();
}
if (actionEvent.getSource() == toInstructionsButton && !instructions) {
instructions = true;
window.remove(toInstructionsButton);
repaint();
}
if (actionEvent.getSource() == tmMove) {
if (gameRunning) {
snakeMove();
checkFruit();
checkCollisions();
} else {
repaint();
}
repaint();
}
}
@Override
public void keyTyped(KeyEvent ke) {
}
@Override
public void keyPressed(KeyEvent ke) {
if (ke.getKeyCode() == KeyEvent.VK_SPACE && !gameRunning && instructions) {
snakeMove();
checkFruit();
checkCollisions();
newFruit();
gameRunning = true;
instructions = false;
}
if (ke.getKeyCode() == KeyEvent.VK_SPACE && gameRunning) {
if (gameStarted) {
gameStarted = false;
tmMove.stop();
} else {
tmMove.start();
gameStarted = true;
}
}
if (gameStarted) {
switch (ke.getKeyCode()) {
case KeyEvent.VK_RIGHT:
case KeyEvent.VK_D:
directions.add("H");
break;
case KeyEvent.VK_LEFT:
case KeyEvent.VK_A:
directions.add("V");
break;
case KeyEvent.VK_UP:
case KeyEvent.VK_W:
directions.add("U");
break;
case KeyEvent.VK_DOWN:
case KeyEvent.VK_S:
directions.add("N");
break;
case KeyEvent.VK_E:
tmMove.setDelay(200);
break;
case KeyEvent.VK_M:
tmMove.setDelay(150);
break;
case KeyEvent.VK_H:
tmMove.setDelay(100);
break;
}
}
}
@Override
public void keyReleased(KeyEvent ke) {
}
}
其他一些注意事项如下:
- 应通过
SwingUtilities.invokeLater
在 EDT 上创建所有 swing 组件
- 不要使用
setBounds
或setSize
,使用适当的 layout manager 并在添加所有组件之后并使其可见之前调用JFrame#pack()
- 覆盖
getPreferredSize
而不是调用setPreferredSize
- 您不需要使用图形来完成简单的事情,例如绘制字符串,只需使用
JLabel
并将其添加到具有适当布局的JPanel
- 您可以将
Graphics
对象投射到Graphics2D
对象,并将一些 anti-aliasing and other rendering hints 投射到您的绘图中,使其看起来更清晰 - 您应该使用 KeyBindings 而不是
KeyListener
- 分离您的代码问题,不要将一个
actionPerformed
用于所有目的,例如计时器和按钮回调,使用任意 类 以使代码更简洁