我正在尝试制作2048游戏。我制作了终端版本来检查我的游戏逻辑,一切似乎都正常。现在我添加了 GameFrame 和 GamePanel 类。我的目标是能够单击箭头键将图块移动到所需的方向。我当前的问题是,运行我的代码时它永远不会到达我的 actionPerformed 函数。我正在使用阻塞队列来等待用户决定下一步行动。运行我的代码时,面板会显示两个初始图块(应该如此),但是当我单击任何箭头时,没有任何变化。通过 print 语句,我可以看到它没有到达 actionPerformed 函数,但我不明白为什么。根据我的理解,这个函数应该每 75 毫秒运行一次(这是我选择的延迟),并且我设置了 setFocusable(true),所以我认为它应该监听键盘事件。在另一篇文章中,我读到了一个类似的问题,它说要使用 addActionListener,但我不确定如果我缺少什么,我应该在哪里添加它。任何帮助表示赞赏!我将添加所有 GamePanel、GameFrame 类和 main:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class GamePanel extends JPanel implements ActionListener {
static final int SCREEN_WIDTH = 600;
static final int SCREEN_HEIGHT = 600;
static final int DELAY = 75;
BlockingQueue<Boolean> queue;
Timer timer;
boolean running = false;
Random random;
Board grid;
GamePanel(){
random = new Random();
this.setPreferredSize(new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT));
this.setBackground(Color.white);
this.setFocusable(true);
this.queue = new LinkedBlockingQueue<>();
this.addKeyListener(new MyKeyAdapter());
startGame();
}
public void startGame(){
grid = new Board();
grid.newTile();
grid.newTile();
running = true;
timer = new Timer(DELAY, this);
timer.start();
System.out.println("game started");
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
int tileSize = SCREEN_WIDTH / this.grid.SIZE; // Calculate tile size based on panel size and board size
for (int row = 0; row < this.grid.SIZE; row++) {
for (int col = 0; col < this.grid.SIZE; col++) {
int x = col * tileSize;
int y = row * tileSize;
g2d.drawRect(x, y, tileSize, tileSize);
}
}
for (int row = 0; row < this.grid.SIZE; row++) {
for (int col = 0; col < this.grid.SIZE; col++) {
int x = col * tileSize;
int y = row * tileSize;
Tile tile = grid.board[row][col];
if (tile != null) {
g2d.setColor(tile.getColor());
g2d.fillRect(x, y, tileSize, tileSize);
g2d.setColor(Color.BLACK);
g2d.drawString(String.valueOf(tile.getValue()), x + tileSize / 2, y + tileSize / 2);
}
}
}
}
@Override
public void actionPerformed(ActionEvent e) {
try{
boolean running = queue.take();
System.out.println("I am in the try");
if(running){
System.out.println("running is true");
grid.move();
grid.newTile();
if (!grid.gridHasSpace()) {
//TODO Handle game over logic here
queue.put(false);
}
}
repaint();
} catch (InterruptedException e1){
//TODO
}
}
public class MyKeyAdapter extends KeyAdapter{
@Override
public void keyPressed(KeyEvent e){
switch (e.getKeyCode()) {
case KeyEvent.VK_LEFT:
grid.setDirection('L');
System.out.println("Left key pressed");
break;
case KeyEvent.VK_RIGHT:
grid.setDirection('R');
break;
case KeyEvent.VK_UP:
grid.setDirection('U');
break;
case KeyEvent.VK_DOWN:
grid.setDirection('D');
break;
}
try {
queue.put(true);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
}
import javax.swing.JFrame;
public class GameFrame extends JFrame {
GameFrame(){
this.add(new GamePanel());
this.setTitle("Game2048");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setResizable(false);
this.pack();
this.setVisible(true);
this.setLocationRelativeTo(null);
}
}
import javax.swing.SwingUtilities;
public class Game2048 {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {GameFrame gameFrame = new GameFrame();});
}
来自 JavaDocs 的
BlockQueue
E采取() 抛出 InterruptedException
检索并删除此队列的头部,必要时等待,直到元素变得可用。
(重点是我添加的)
在您的代码上下文中,
take
将阻塞事件调度线程,防止发生任何进一步的事件处理(即按键或绘制事件)。
在现有的背景下,这并没有什么意义。如果您要做的只是等待状态更改,为什么要使用
Timer
?为什么不简单地在触发时使用按键处理程序触发方法呢?
我也不鼓励使用
KeyListener
,如果没有其他原因,那么它创建的所有与焦点相关的问题都已由 Keybindings API 解决了。
这还允许您将操作与源/触发器解耦,并允许您重新使用逻辑。这可能意味着您还可以将按钮控件添加到 UI 作为用户输入的另一种方式,并为按钮和键绑定重新使用
Action
。
作为一个概念示例...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.Random;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame();
frame.add(new GamePanel());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class GamePanel extends JPanel { //implements ActionListener {
enum Direction {
LEFT, RIGHT, UP, DOWN;
}
static final int SCREEN_WIDTH = 600;
static final int SCREEN_HEIGHT = 600;
private boolean running = false;
private Random random;
GamePanel() {
random = new Random();
this.setBackground(Color.white);
//this.queue = new LinkedBlockingQueue<>();
InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = getActionMap();
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), Direction.LEFT);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), Direction.RIGHT);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), Direction.UP);
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), Direction.DOWN);
actionMap.put(Direction.LEFT, new GridAction('L'));
actionMap.put(Direction.RIGHT, new GridAction('R'));
actionMap.put(Direction.UP, new GridAction('U'));
actionMap.put(Direction.UP, new GridAction('D'));
startGame();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT);
}
public void startGame() {
running = true;
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
//int tileSize = SCREEN_WIDTH / this.grid.SIZE; // Calculate tile size based on panel size and board size
//
//for (int row = 0; row < this.grid.SIZE; row++) {
// for (int col = 0; col < this.grid.SIZE; col++) {
// int x = col * tileSize;
// int y = row * tileSize;
// g2d.drawRect(x, y, tileSize, tileSize);
// }
//}
//
//for (int row = 0; row < this.grid.SIZE; row++) {
// for (int col = 0; col < this.grid.SIZE; col++) {
// int x = col * tileSize;
// int y = row * tileSize;
// Tile tile = grid.board[row][col];
// if (tile != null) {
// g2d.setColor(tile.getColor());
// g2d.fillRect(x, y, tileSize, tileSize);
// g2d.setColor(Color.BLACK);
// g2d.drawString(String.valueOf(tile.getValue()), x + tileSize / 2, y + tileSize / 2);
// }
// }
//}
}
protected void directionDidChange(char direction) {
if (!running) {
return;
}
//grid.setDirection(direction);
// Do what ever other processing your need to do
repaint();
}
protected class GridAction extends AbstractAction {
// I'd use a enum for this purpose, but that's me
private char direction;
public GridAction(char action) {
this.direction = action;
}
public char getDirection() {
return direction;
}
@Override
public void actionPerformed(ActionEvent e) {
directionDidChange(getDirection());
}
}
}
}
如果您需要
Timer
根据当前状态执行不同的操作,那么我会利用 Set
来跟踪当前按下了哪些键。这将要求您跟踪新闻/发布状态,因此它确实增加了一些复杂性。 例如