您如何让您的程序休眠以便 GUI 可以赶上正在发生的事情?

How can you make your program sleep so that the GUI can catch up to what's happening?

我正在制作一个贪吃蛇游戏(for those who don't know),其中蛇由 AI 使用不同的算法控制来捕捉食物。与常规游戏的不同之处在于,除非 AI 发出命令,否则蛇不会移动。

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

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

如何让程序休眠以便 GUI 可以跟上正在发生的事情?

编辑:

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

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:另外,我只是想指出,我试图在不依赖机器人的情况下移动,但无济于事。

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

一种简单的方法是使用 Swing Timer,它可以定期触发对侦听器的更新。它的好处是可以在 EDT 的上下文中触发,从而可以安全地从内部更新 UI。有关详细信息,请参阅 How to use Swing Timers

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

每次你的游戏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

您需要了解某种游戏调度程序循环并了解其工作原理。

这是 java swing 的一些示例代码:

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

另一种简化调度程序的方法是首先让您的游戏基于回合制。也就是说,当玩家移动 1 圈(即输入)时,游戏会进行所有处理并阻止用户输入,直到处理完成(即单循环)。

第 1 部分:

Thread.sleep之间的差异:

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

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

  • 例如使用线程时(遵循代码)

    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
    });
    

    然后仅此线程冻结(给定时间)或(无论您将其转换为冻结)。

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

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

的部分

你想要的游戏,但你的程序需要工作的 主线程 仍然可以工作 (如果它是 window 或任何...)

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

     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 部分:(当我制作 类似的游戏时 我做了什么) 几个月前我尝试了一个类似的 game.The 基本想法是玩家必须通过迷宫所以....

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

     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
        }
      }
    

这就是我认为的游戏的基本理念。 Thread 这是一个额外的选项,我帮不上什么忙,你必须找到方法...