问题描述
我的蛇游戏遇到了这个问题,例如,当您向左移动并快速向上和向右按下时,它会自行解决,因为我只告诉游戏如果向左移动,我无法向右按,因此,如果我在按右之前先按上,它允许我这样做,使蛇进入自己。
因此,当您运行该程序时,只需按 Next 并按空格键,游戏就应该开始了,然后当您向左移动时,只需在此之后快速向上和向右按一下,然后自己查看即可。不幸的是,我不知道如何解决这个问题,因为我们只学习了 6 个月的 Java,而且我们只真正学习了 if 等基础知识。如果您有任何问题,我会很快回答。
解决方法
这难道不是正确的解决方案吗?
如果有人转弯太快,比如向左行驶时,“向上”和“向右”太快,蛇仍然在同一个 y 轴上,因为它尚未随着向上运动而改变,并且'右' 键按下,蛇试图在 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],0 <-- coordinates before turn
680,0 | 720,0 <-- coordinates after down + right turn
对我来说,这种行为看起来应该是这样,除非我遗漏了什么。如果您想避免这种情况,请尝试在按键后添加一小部分延迟。
,我对您的代码进行了一些更改,现在可以按照您的预期正常工作。
本质上的问题是,如果在计时器计时并调用 snakeMove
之前足够快地按下 2 个键,您将覆盖 direction
变量,因此将“错过”一个键。>
想象一下这些步骤发生了:
-
direction
是 V,蛇向左走 - 计时器滴答并调用
snakeMove
,该方法计算direction
,即 V 所以蛇继续向左移动 - 在计时器再次计时之前,我在“同一时间”按 up + right。因此,在计时器再次计时之前会发生 2 个事件:
- 处理第一个键,因此
direction
设置为 updirection == "U"
- 处理第二个键,因此
direction
设置为 rightdirection == "H"
- 处理第一个键,因此
- 现在只有计时器再次计时并且调用了
snakeMove
。该方法在 switch 语句中计算direction
和direction == "H"
,因此我们“错过”了direction == "U"
,因为它被keyPressed
方法中的第二次按键覆盖计时器打勾
为了克服这个问题,正如我在上一个问题中所建议的那样,使用 FIFO(先进先出)列表来正确处理所有键,这样它们就不会被“遗漏”。
这可以使用具有我们需要的 pop()
函数的 LinkedList 来完成。
因此,在您的代码中,我将全局变量 direction
重命名为 currentDirection
:
private String currentDirection;
并删除了 static
修饰符,因为这不是必需的,我还删除了 static
上的 snakeMove
修饰符,因为这又是不需要的,并阻止我们访问实例变量,即 {{ 1}}。我还将范围更改为 currentDirection
,如您展示的代码段所示,它没有必要设为 private
,但这些更改只是为了提高代码的正确性。然后我创建了一个全局变量:
public
然后到处(除了在 private LinkedList<String> directions = new LinkedList<>();
方法中)我删除了 snakeMove
并将其替换为 currentDirection =
,因此我们不再更改单个变量,而是将每个方向添加到我们的先进先出/directions.add(...)
。我还删除了您在 LinkedList
中所做的一些 if 检查,因为这不是必需的,即使蛇与按下的键朝同一方向移动 - 谁在乎,只需将其添加到按下的键列表中以便我们稍后在 keyPressed
然后在您的 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)); g.fillRect(0,WIDTH,(HEIGHT - 40)); g.setColor(new Color(63,116,41,255)); g.fillRect(0,HEIGHT - 40,(2)); g.setColor(new Color(0,240)); g.fillRect(0,HEIGHT - 38,38); g.setColor(new Color(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",18)); for (int i = 0; i < snakeParts; i++) { if (i == 0) { g.setColor(Color.RED); g.fillOval(x[i],y[i],bodySize); } else { g.setColor(Color.PINK); g.fillOval(x[i],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,HEIGHT); g.setColor(Color.WHITE); g.setFont(new Font("Arial",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,55)); toInstructionsButton.setForeground(Color.BLACK); toInstructionsButton.setFont(new Font("Arial",60)); toInstructionsButton.addActionListener(this); } public void instructions(Graphics g) { g.setFont(new Font("Arial",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,HEIGHT); g.setColor(Color.red); g.setFont(new Font("Arial",65)); FontMetrics metrics = getFontMetrics(g.getFont()); g.drawString("Du dog!",(WIDTH - metrics.stringWidth("Du dog!")) / 2,(HEIGHT - 40) / 2); g.setColor(new Color(44,255)); g.setFont(new Font("Arial",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) { } }
在 EDT 上创建 - 不要使用
SwingUtilities.invokeLater
或setBounds
,使用适当的 layout manager 并在添加所有组件之后和使其可见之前调用setSize
- 覆盖
JFrame#pack()
而不是调用getPreferredSize
- 您不需要使用图形来执行绘制字符串等简单的操作,只需使用
setPreferredSize
并将其添加到具有适当布局的JLabel
中 - 您可以将
JPanel
对象投射到Graphics
对象,将一些 anti-aliasing and other rendering hints 投射到绘图中,使其看起来更清晰 - 您应该使用 KeyBindings 而不是
Graphics2D
- 分离您的代码关注点,不要将一个
KeyListener
用于所有目的,例如计时器和按钮回调,使用任意类使代码更干净简洁