如何使程序休眠,以便GUI能够赶上正在发生的事情?



我正在制作一个贪吃蛇游戏(对于那些不知道的人),其中蛇由AI控制,使用不同的算法来捕捉食物。与常规游戏的不同之处在于,除非AI发送了命令,否则蛇不会移动。

我的问题是,一旦我运行人工智能,人工智能就会创建一堆命令来执行以捕捉食物,但我的GUI只是冻结;可能是因为它跟不上移动堆栈引起的重绘量。通过控制台日志,我可以看到AI和游戏逻辑仍在运行。

我试图在每次移动后执行Thread.sleep()但我想这只会使包括 GUI 在内的整个程序进入睡眠状态。我对我的paintComponent也有Timer,但这似乎并没有改变任何事情。

如何使程序休眠,以便GUI能够赶上正在发生的事情?

编辑:

好的伙计们,我尝试了您的解决方案,但它仍然无法正常工作。我真的不想只是在这里转储代码,但我真的很迷茫。我有一个计时器,应该以 140 毫秒的间隔重新绘制(这是 DELAY 的值),命令在不同的线程上发送,该线程在每次按键 1000 毫秒后进入睡眠状态,每次调用 move()后我调用 repaint()...这是相关代码(原始代码,此处没有我的修改):

private void initGame() {
    dots = 5;
    for (int z = 0; z < dots; z++) {
        x[z] = 50 - z * 10;
        y[z] = 50;
    }
    locateApple();
    for (int k = blockNb - 1; k > 0; k--) {
        locateBlock(k, apple_x, apple_y);
    }
    if (blocks) {
        locateBlock(0, apple_x, apple_y);
    }
    timer = new Timer(DELAY, this);
    timer.start();
    startAi();
}
// AI CONTROLLER
public void startAi() {
    Ai theAi = new Ai();
    String move = "";
    switch (ai) {
    case "BFS":
        move = theAi.bfs(this);
        break;
    }
    //AI returns a string where each char is a move command
    autoMove(move);
}
public void autoMove(String move) {
    try {
        Robot robot = new Robot();
        System.out.println(move);
        if (move != "#No") {
            Thread thread = new Thread(new Runnable() {
                public void run() {
                    for (int j = 0; j < move.length(); j++) {
                        if (move.charAt(j) == 'l') {
                            robot.keyPress(KeyEvent.VK_LEFT);
                            robot.keyRelease(KeyEvent.VK_LEFT);
                        }
                        if (move.charAt(j) == 'r') {
                            robot.keyPress(KeyEvent.VK_RIGHT);
                            robot.keyRelease(KeyEvent.VK_RIGHT);
                        }
                        if (move.charAt(j) == 'u') {
                            robot.keyPress(KeyEvent.VK_UP);
                            robot.keyRelease(KeyEvent.VK_UP);
                        }
                        if (move.charAt(j) == 'd') {
                            robot.keyPress(KeyEvent.VK_DOWN);
                            robot.keyRelease(KeyEvent.VK_DOWN);
                        }
                        try {
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    }
                }
            });
            thread.run();
        }
    } catch (AWTException e) {
        e.printStackTrace();
    }
}
@Override
public void paintComponent(Graphics g) {
    super.paintComponent(g);
    doDrawing(g);
}
private void doDrawing(Graphics g) {
    if (inGame) {
        g.drawImage(apple, apple_x, apple_y, this);
        for (int j = 0; j < blockNb; j++) {
            g.drawImage(block, block_x[j], block_y[j], this);
        }
        for (int z = 0; z < dots; z++) {
            if (z == 0) {
                g.drawImage(head, x[z], y[z], this);
            } else {
                g.drawImage(ball, x[z], y[z], this);
            }
        }
        Toolkit.getDefaultToolkit().sync();
    } else {
        // gameOver(g);
    }
}

private void move() {
    for (int z = dots; z > 0; z--) {
        x[z] = x[(z - 1)];
        y[z] = y[(z - 1)];
    }
    if (leftDirection) {
        x[0] -= DOT_SIZE;
    }
    if (rightDirection) {
        x[0] += DOT_SIZE;
    }
    if (upDirection) {
        y[0] -= DOT_SIZE;
    }
    if (downDirection) {
        y[0] += DOT_SIZE;
    }
}


@Override
public void actionPerformed(ActionEvent e) {
    if (inGame) {
        repaint();
    }
}
private class TAdapter extends KeyAdapter {
    @Override
    public void keyPressed(KeyEvent e) {
        int key = e.getKeyCode();
        if ((key == KeyEvent.VK_LEFT) && (!rightDirection)) {
            leftDirection = true;
            upDirection = false;
            downDirection = false;
        }
        if ((key == KeyEvent.VK_RIGHT) && (!leftDirection)) {
            rightDirection = true;
            upDirection = false;
            downDirection = false;
        }
        if ((key == KeyEvent.VK_UP) && (!downDirection)) {
            upDirection = true;
            rightDirection = false;
            leftDirection = false;
        }
        if ((key == KeyEvent.VK_DOWN) && (!upDirection)) {
            downDirection = true;
            rightDirection = false;
            leftDirection = false;
        }
        move();
        checkApple();
        checkCollision();
        repaint();
    }
}

编辑2:另外,我只是想指出,我试图在不依赖机器人的情况下移动,但无济于事。

1 部分:

Thread.sleep 之间的区别:

  • 当你在线程(java用来运行程序的线程)中使用它时

然后你的整个程序就会因为这个原因而冻结。

  • 例如,当您使用 A 线程时(遵循代码)

    new Thread(new Runnable(){
     public void run(){
       //do something...
        while(flag==false)
         Thread.sleep(a given time) //it need and try catch
        else
          //do your move
    });
    

    然后只有这个线程冻结(在给定的时间内)或(无论你把它转换为冻结什么)。

在您的情况下,您可以使用一个标志,以便每次用户点击命令时

标志为真,然后再次为假以保持停止部分

您想要的游戏,但您的程序需要工作的主线程仍然有效(如果是窗户或其他任何东西...

  • 第 2 部分:(线程的基本形式)(我使用的标志必须被所有方法看到)(公共或私有)

     new Thread(new Runnable() {
            public void run() {
          flag=true;
     while(flag==true){
         if(move.equals("yes")){
            //your code
           move="no";
         }else
          Thread.sleep(20); //Sleep For a While(and then check again)
     }
    //That means that your Thread will run all over your game
    //until game over (when game over just do the flag=false;)
    //and The Thread will stop-exit
        }});
    
*关于重绘方法

(不要太快调用重绘方法)仅在玩家移动时调用它(这是帧的时间需要重新绘制[好吧,如果您的游戏中有.gif图像,则看不到此图像]

  • 第3部分:(当我制作类似的游戏时,我做了什么)几个月前,我尝试了类似的游戏。基本的想法是一个必须通过迷宫的玩家,所以......

    对于每个级别,我都有一个这样的班级...

     public abstarct class Level2(){
     //The game was into a panel like your.
     //Here i added .setFocusable and .addKeyListener to panel
      //Here it was an
      public void actionListener(){
          //The actionListener checked if the player found the door(for next level)
    
      //Here it was the repaint so every time something happened repaint()
    
       repaint();
      }//End of Action Listener
    
     //Use the paint or paintComponent what you need..
      public void paint[or paintComponent](Graphics g){
          //the 'squares' and the icons the had to be *repainted* again
      }
    
     //Here i had an extra class into this for the KeyListeners
     //And I added the KeyListener like your game(up,down,left,right)
     //I i said before i added this KeyListener to the panel
        private class TAdapter extends KeyAdapter {
            //KeyPressed,Released...etc
        }
      }
    

这是基本的想法,我认为对于你的游戏来说。线程这是一个额外的选择,我帮不了更多,你必须找到方法......

你应该有一个"update"循环,从中执行更新命令和重绘请求。

一个简单的方法是使用Swing Timer,它可以定期触发对侦听器的更新。 它的好处是在 EDT 的上下文中触发,因此可以安全地从内部更新 UI。 有关更多详细信息,请参阅如何使用摆动计时器

更复杂的方法是使用包含某种循环的Thread。 这将执行所需的更新并计划重绘,但您需要插入Thread.sleep,以便有时间定期进行更新。 这样做的问题是您需要同步更新,以便在绘画发生时不会更新模型,以及将 UI 更新与 EDT 同步

你需要有某种游戏调度程序循环,并了解它是如何工作的。

以下是 java swing 的一些示例代码:

http://staticvoidgames.com/tutorials/swing/swingTimers

使用调度程序简化事情的另一种方法是首先使您的游戏基于轮流。也就是说,当玩家移动 1 转弯(即输入)时,游戏会完成所有处理并阻止用户输入,直到处理完成(即单循环)。

每次你的游戏AI输出一个新动作时,你都需要调用paint,但是你还需要等待绘画完成后调用的任何事件,然后再输出你的下一个动作,你可能想再等一秒钟什么的,玩一玩。

伪代码

def fun 0 // entry point for program
  // hook paint done event to func 1
  call fun 2
def fun 1 // event handler method for when painting done
  call fun 2
def fun 2 // function that does AI work
  // do game logic
  // call paint

最新更新