JavaFX 如何处理帧率非常高的输入?

如何解决JavaFX 如何处理帧率非常高的输入?

我在 Ubuntu 20.04 上使用 OpenJavaFX。我想让用户按下退出键来切换菜单的显示。由于帧速率非常高,我正在努力实现这一目标。

简单的程序:

class 
AutoScalingCanvas extends Region {

  private final Canvas canvas;

  public AutoScalingCanvas(double canvasWidth,double canvasHeight) {
    this.canvas = new Canvas(canvasWidth,canvasHeight);
    getChildren().add(canvas);
  }

  public GraphicsContext getGraphicsContext2D() {
    return canvas.getGraphicsContext2D();
  }

  @Override
  protected void layoutChildren() {
    double x = getInsets().getLeft();
    double y = getInsets().getTop();
    double w = getWidth() - getInsets().getRight() - x;
    double h = getHeight() - getInsets().getBottom() - y;

    // preserve aspect ratio while also staying within the available space
    double sf = Math.min(w / canvas.getWidth(),h / canvas.getHeight());
    canvas.setScaleX(sf);
    canvas.setScaleY(sf);

    positionInArea(canvas,x,y,w,h,-1,HPos.CENTER,VPos.CENTER);
  }
}


public class 
Gui extends Application
{
  long target_ns_per_frame = 1_000_000_00 / 60;
  boolean in_menu;
  boolean esc_down;

  @Override
  public void
  start(Stage primary_stage) throws Exception
  {
    primary_stage.setTitle("GUI");

    AutoScalingCanvas canvas = new AutoScalingCanvas(1280,720);
    Scene scene = new Scene(canvas);
    scene.setFill(Color.BLACK);
    primary_stage.setScene(scene);

    GraphicsContext gc = canvas.getGraphicsContext2D();

    scene.setOnKeyPressed(new EventHandler<KeyEvent>(){
      @Override
      public void handle(KeyEvent event)
      {
        esc_down = (event.getCode() == KeyCode.ESCAPE);
      }
    });

    scene.setOnKeyReleased(new EventHandler<KeyEvent>(){
      @Override
      public void handle(KeyEvent event)
      {
        if (event.getCode() == KeyCode.ESCAPE)
        {
          esc_down = false;
        }
      }
    });

    new AnimationTimer()
    {
      @Override
      public void
      handle(long total_elapsed_time_ns)
      {
        gc.setFill(Color.WHITE);
        gc.fillRect(0,1280,720);

        if (esc_down)
        {
          in_menu = !in_menu;
        }

        if (in_menu)
        {
          gc.setFill(Color.BLUE);
          gc.fillRect(300,300,200,200);
        }
        else
        {
          gc.setFill(Color.RED);
          gc.fillRect(100,100,100);
        }

        long elapsed_time_ns = System.nanoTime() -
                               total_elapsed_time_ns;
        if (elapsed_time_ns < target_ns_per_frame)
        {
          long time_remaining_ms = (target_ns_per_frame - elapsed_time_ns)
                                    / 1000;
          try {
            Thread.sleep(time_remaining_ms);
          }
          catch (InterruptedException e)
          {

          }
        }
      }
    }.start();

    primary_stage.show();
  }
}

如果在没有 Thread.sleep() 的情况下运行,帧速率约为 600fps。结果,按一次退出键将被视为在许多帧内按下(由于我的人类手指的速度限制),从而多次触发切换。这显然不是故意的。所以,我试图将帧速率限制在 60fps。然而,随着睡眠,程序运行速度非常慢(也许我在错误的线程上睡觉?)

如何最好地跟踪输入以实现这种切换行为?

解决方法

首先,您不应该通过调用 Thread.sleep() 来阻塞 FX 应用程序线程。这将阻止更新 UI 或处理事件,直到 sleep() 完成。

如果只是想在用户每次按下 ESCAPE 键时切换菜单,那么您的代码就太复杂了。只需切换一个标志,指示是否应在 onReleased 处理程序中绘制菜单,然后检查 AnimationTimer.handle() 中的标志:

public class Gui extends Application {
    
    boolean inMenu;

    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.setTitle("GUI");

        AutoScalingCanvas canvas = new AutoScalingCanvas(1280,720);
        Scene scene = new Scene(canvas);
        scene.setFill(Color.BLACK);
        primaryStage.setScene(scene);

        GraphicsContext gc = canvas.getGraphicsContext2D();


        scene.setOnKeyReleased(event -> {
            if (event.getCode() == KeyCode.ESCAPE) {
                inMenu = ! inMenu;
            }
        });

        new AnimationTimer() {
            @Override
            public void handle(long now) {
                gc.setFill(Color.WHITE);
                gc.fillRect(0,1280,720);

                if (inMenu) {
                    gc.setFill(Color.BLUE);
                    gc.fillRect(300,300,200,200);
                } else {
                    gc.setFill(Color.RED);
                    gc.fillRect(100,100,100);
                }

            }
        }.start();

        primaryStage.show();
    }
}

如果您只想在需要时优化重绘,只需引入另一个指示需要重绘的标志:

public class Gui extends Application {
    
    private boolean inMenu;
    private boolean repaintRequested = true ;

    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.setTitle("GUI");

        AutoScalingCanvas canvas = new AutoScalingCanvas(1280,720);
        Scene scene = new Scene(canvas);
        scene.setFill(Color.BLACK);
        primaryStage.setScene(scene);

        GraphicsContext gc = canvas.getGraphicsContext2D();


        scene.setOnKeyReleased(event -> {
            if (event.getCode() == KeyCode.ESCAPE) {
                inMenu = ! inMenu;
                repaintRequested = true ;
            }
        });

        new AnimationTimer() {
            @Override
            public void handle(long now) {
                if (repaintRequested) {
                    gc.setFill(Color.WHITE);
                    gc.fillRect(0,720);
                    if (inMenu) {
                        gc.setFill(Color.BLUE);
                        gc.fillRect(300,200);
                    } else {
                        gc.setFill(Color.RED);
                        gc.fillRect(100,100);
                    } 
                    repaintRequested = false ;
                }

            }
        }.start();

        primaryStage.show();
    }
}
,

我没有尝试过这些,所以我只能给你一个大致的方向。

您可以在 esc_handled 方法的末尾添加另一个设置为 true 的布尔变量 handle。然后,您可以在该方法中再添加一项检查,如果该事件已被处理,如果已处理,则跳过处理步骤。

以下代码实现了这一点:

  1. 添加变量
    boolean in_menu;
    boolean esc_down;
    boolean esc_handled;
    
  2. 检查 esc_handled(在 handle 内)并将事件设置为已处理
    if (esc_down && !esc_handled)
    {
        in_menu = !in_menu;
        esc_handled = true;
    }
    
  3. 在发布 esc 时将 esc_handled 设置为 false
    scene.setOnKeyReleased(new EventHandler<KeyEvent>(){
          @Override
          public void handle(KeyEvent event)
          {
            if (event.getCode() == KeyCode.ESCAPE)
            {
              esc_down = false;
              esc_handled = false;
            }
          }
        });
    
,

看起来您正在使用动画计时器对输入或输出的状态以及退出键的按下/释放进行某种采样。你根本不需要这样做。

您正在尝试将按键按下/释放事件转换为一种状态,这是有道理的,但您只需要在事件处理程序中切换该状态即可。由于显示/隐藏操作是对事件的响应,因此您可以直接从事件中调用绘制例程。那么事件将切换状态并调用屏幕重绘:

public class Gui extends Application {
    boolean in_menu;
    GraphicsContext gc;

    @Override
    public void start(Stage primary_stage) throws Exception {
        primary_stage.setTitle("GUI");

        AutoScalingCanvas canvas = new AutoScalingCanvas(1280,720);
        primary_stage.setScene(new Scene(canvas));
        gc = canvas.getGraphicsContext2D();
        showOrHideMenu();
        new Scene(canvas).setOnKeyPressed(event -> {
            if (event.getCode() == KeyCode.ESCAPE) {
                in_menu = !in_menu;
                showOrHideMenu();
            }
        });
        primary_stage.show();
    }

    private void showOrHideMenu() {
        gc.setFill(Color.WHITE);
        gc.fillRect(0,720);
        if (in_menu) {
            gc.setFill(Color.BLUE);
            gc.fillRect(300,200);
        } else {
            gc.setFill(Color.RED);
            gc.fillRect(100,100);
        }
    }
}

或者(这可能更像是“JavaFX”),您可以使输入/输出菜单状态可观察,然后在状态上放置一个更改侦听器以进行重绘:

public class Gui extends Application {
    BooleanProperty in_menu = new SimpleBooleanProperty(false);
    GraphicsContext gc;

    @Override
    public void start(Stage primary_stage) throws Exception {
        primary_stage.setTitle("GUI");

        AutoScalingCanvas canvas = new AutoScalingCanvas(1280,720);
        Scene scene = new Scene(canvas);
        primary_stage.setScene(scene);
        gc = canvas.getGraphicsContext2D();
        showOrHideMenu(false);
        scene.setOnKeyPressed(event -> {
            if (event.getCode() == KeyCode.ESCAPE) {
                in_menu.set(!in_menu.get());
            }
        });
        in_menu.addListener(((observable,oldValue,newValue) -> showOrHideMenu(newValue)));
        primary_stage.show();
    }

    private void showOrHideMenu(boolean inMenu) {
        gc.setFill(Color.WHITE);
        gc.fillRect(0,720);
        if (inMenu) {
            gc.setFill(Color.BLUE);
            gc.fillRect(300,100);
        }
    }
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-
参考1 参考2 解决方案 # 点击安装源 协议选择 http:// 路径填写 mirrors.aliyun.com/centos/8.3.2011/BaseOS/x86_64/os URL类型 软件库URL 其他路径 # 版本 7 mirrors.aliyun.com/centos/7/os/x86
报错1 [root@slave1 data_mocker]# kafka-console-consumer.sh --bootstrap-server slave1:9092 --topic topic_db [2023-12-19 18:31:12,770] WARN [Consumer clie
错误1 # 重写数据 hive (edu)&gt; insert overwrite table dwd_trade_cart_add_inc &gt; select data.id, &gt; data.user_id, &gt; data.course_id, &gt; date_format(
错误1 hive (edu)&gt; insert into huanhuan values(1,&#39;haoge&#39;); Query ID = root_20240110071417_fe1517ad-3607-41f4-bdcf-d00b98ac443e Total jobs = 1
报错1:执行到如下就不执行了,没有显示Successfully registered new MBean. [root@slave1 bin]# /usr/local/software/flume-1.9.0/bin/flume-ng agent -n a1 -c /usr/local/softwa
虚拟及没有启动任何服务器查看jps会显示jps,如果没有显示任何东西 [root@slave2 ~]# jps 9647 Jps 解决方案 # 进入/tmp查看 [root@slave1 dfs]# cd /tmp [root@slave1 tmp]# ll 总用量 48 drwxr-xr-x. 2
报错1 hive&gt; show databases; OK Failed with exception java.io.IOException:java.lang.RuntimeException: Error in configuring object Time taken: 0.474 se
报错1 [root@localhost ~]# vim -bash: vim: 未找到命令 安装vim yum -y install vim* # 查看是否安装成功 [root@hadoop01 hadoop]# rpm -qa |grep vim vim-X11-7.4.629-8.el7_9.x
修改hadoop配置 vi /usr/local/software/hadoop-2.9.2/etc/hadoop/yarn-site.xml # 添加如下 &lt;configuration&gt; &lt;property&gt; &lt;name&gt;yarn.nodemanager.res