Search code examples
javajavafxtimelinekeyframe

Cannot make the KeyFrame vary in Timeline JavaFX


I'm making a reaction time application and I need to display this in the GUI so I need to use a Timeline to do this. I have made everything work with the Timeline, other than allowing the time between KeyFrames to be variable.

In the code below, I managed to make it change after the first KeyFrame (so there is no wait, and then a wait time of 'waitTime' seconds). It then uses the value of 'waitTime' and keeps the time between KeyFrames constant from here. In the code is what I am doing to change the variable 'waitTime'. I am just unsure how I can change the KeyFrame time. I think I need a recursive way of doing this but have not yet been able to find anything anywhere on this topic.

double waitTime;

    Timeline attackChanger = new Timeline(new KeyFrame(Duration.ZERO, new EventHandler<ActionEvent>() {

    @Override
    public void handle(ActionEvent event){
        
        
        //randomly generate attack
        if(isCountdown = true){
            int randI = rand.nextInt(10);
            waitTime = randI;
            System.out.println(waitTime);
            String attack = utils.getRandomAttack();
            attackLabel.setText(attack);
        }
        
        
    }
}), new KeyFrame(Duration.seconds(waitTime)));

Thank you in advance for any answers :)


Solution

  • After you start the Timeline the KeyFrames are fixed. Even if they were not there's no passing of primitive types by reference in java, i.e. Duration.seconds(waitTime) returns a Duration object with constant duration no matter what you do to waitTime later. While the animation is running modifying the list of KeyFrames does not have any effect either.

    The probably best way of going about this would be to adjust the rate property of the timeline according to the new value.

    Assuming you use

    attackChanger.setCycleCount(Animation.INDEFINITE);
    

    on your animation:

    Timeline attackChanger = new Timeline();
    attackChanger.getKeyFrames().addAll(new KeyFrame(Duration.ZERO, new EventHandler<ActionEvent>() {
    
        @Override
        public void handle(ActionEvent event){
            
            
            //randomly generate attack
            if(isCountdown = true){
                int randI;
    
                // we need treat 0 immediately to avoid
                // setting the rate to infinity
                // note: in this case only a single
                // text value will be displayed and you probably should
                // go with (rand.nextInt(9) + 1) to generate values between
                // 1 and 9 (inclusive)
                // I keep it this way though because of the console output
                // and possible side effects of utils.getRandomAttack()
                do {
                    randI = rand.nextInt(10);
                    waitTime = randI;
                    System.out.println(waitTime);
                    String attack = utils.getRandomAttack();
                    attackLabel.setText(attack);
                } while (randI == 0);
    
                // (effective cycle duration) = (standard duration) / rate
                attackChanger.setRate(1d / randI);
            }
            
            
        }
    }), new KeyFrame(Duration.seconds(1)));
    attackChanger.setCycleCount(Animation.INDEFINITE);
    
    //make sure `waitTime` is not 0 at this point
    attackChanger.setRate(1d / waitTime);