AlmasB / FXGL

Java / JavaFX / Kotlin Game Library (Engine)
http://almasb.github.io/FXGL/
MIT License
4.45k stars 555 forks source link

Fade out custom loading scene when loading is completed #1387

Closed almost-wizard closed 2 months ago

almost-wizard commented 2 months ago

Hello! I have a question. I haven't quite figured out if there is an option to add a fade animation (or any other animation) at the moment when the loading in the "CustomLoadingScene" component has completed?

For example, with class bellow I provided to the game my own loading scene:

import com.almasb.fxgl.animation.Animation;
import com.almasb.fxgl.animation.Interpolators;
import com.almasb.fxgl.app.scene.LoadingScene;
import com.almasb.fxgl.dsl.FXGL;
import com.almasb.fxgl.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.util.Duration;
import org.jetbrains.annotations.NotNull;

import static com.almasb.fxgl.dsl.FXGLForKtKt.animationBuilder;

public class Loading extends LoadingScene {

    private final Rectangle[] dots;
    Animation<?> animation;

    public Loading() {
        getContentRoot().getChildren().addAll(new Rectangle(getAppWidth(), getAppHeight()));

        dots = new Rectangle[3];

        for (int i = 0; i < 3; i++) {
            dots[i] = new Rectangle(20, 20, Color.DARKGREEN);
            dots[i].setTranslateX(getAppWidth() - 300 + i * 40);
            dots[i].setTranslateY(300);

            animationBuilder(this)
                    .delay(Duration.seconds(i * 0.5))
                    .interpolator(Interpolators.CUBIC.EASE_OUT())
                    .duration(Duration.seconds(1.36))
                    .repeatInfinitely()
                    .autoReverse(true)
                    .fadeIn(dots[i])
                    .buildAndPlay();

            getContentRoot().getChildren().add(dots[i]);
        }
    }

    @Override
    public void onExitingTo(@NotNull Scene nextState) {
        animationBuilder(this)
                .delay(Duration.seconds(5))
                .fadeOut(dots)
                .buildAndPlay();
    }
}

I tried to implement a fade animation in the onExitingTo(@NotNull Scene nextStage) method, but it didn't work out. Therefore, I want to ask you - is there such a possibility at all? Thank you in advance.

AlmasB commented 2 months ago

Hi, the trick is to animate while you are still in the loading scene, rather than when you are exiting it. Track your loading progress, then when the load is complete, play the fade out animation.

I don't quite remember what triggers it to go from load to play mode, it might be the length of initGame(). So when your initGame() is done, ask the loading scene to play the fade out animation, keep initGame() waiting for the exact duration (e.g. Thread.sleep()), then they both should seamlessly transition from one to another.

Something like this:

initGame() {
// load game stuff
// we are ready, so find a way to pass data to loading scene to notify it
// Thread.sleep(duration of fade out animation)
}
almost-wizard commented 2 months ago

Thank you for your advice, it really worked! I implemented the transfer of information that the game is loaded from initGame() to LoadingScene using "global" (if you can call them that) variables. Here is the code of my implementation:

part of GameApplication:

public class Runner extends GameApplication {

    // ...

    @Override
    protected void initGameVars(Map<String, Object> vars) {
        vars.put("isGameInitialized", false);
    }

    @Override
    protected void initGame() {
        // rest of my code...

        set("isGameInitialized", true);

        try {
            Thread.sleep(Config.FADE_DURATION);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    // ...
}

And my loading scene code:

package ru.rsreu.gorobchenko.project.scenes;

import com.almasb.fxgl.animation.Interpolators;
import com.almasb.fxgl.app.scene.LoadingScene;
import com.almasb.fxgl.dsl.FXGL;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.util.Duration;
import ru.rsreu.gorobchenko.project.Config;
import ru.rsreu.gorobchenko.project.shared.NodeUtils;

import static com.almasb.fxgl.dsl.FXGLForKtKt.getb;
import static com.almasb.fxgl.dsl.FXGLForKtKt.set;

public class Loading extends LoadingScene {

    private final Pane dotsPane;

    public Loading() {
        getContentRoot().getChildren().add(new Rectangle(getAppWidth(), getAppHeight()));
        dotsPane = new Pane();

        for (int i = 0; i < 3; i++) {
            Rectangle dot = new Rectangle(20, 20, Color.DARKGREEN);
            NodeUtils.bindCenter(dot, getAppWidth() + (i - 1) * 65, getAppHeight());

            FXGL.animationBuilder(this)
                    .delay(Duration.seconds(i * 0.5))
                    .interpolator(Interpolators.CUBIC.EASE_OUT())
                    .duration(Duration.seconds(1.36))
                    .repeatInfinitely()
                    .autoReverse(true)
                    .fadeIn(dot)
                    .buildAndPlay();

            dotsPane.getChildren().add(dot);
        }

        getContentRoot().getChildren().add(dotsPane);
    }

    @Override
    protected void onUpdate(double tpf) {
        if (getb("isGameInitialized")) {
            FXGL.animationBuilder(this)
                    .duration(Duration.millis(Config.FADE_DURATION))
                    .fadeOut(dotsPane)
                    .buildAndPlay();
            set("isGameInitialized", false);
        }
    }
}

Thank you!